//------------------------------------------------------------------------------ // File: WXUtil.cpp // // Desc: DirectShow base classes - implements helper classes for building // multimedia filters. // // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ #include #if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 #include #define STRSAFE_NO_DEPRECATE #include // --- CAMEvent ----------------------- CAMEvent::CAMEvent(BOOL fManualReset, __inout_opt HRESULT *phr) { m_hEvent = CreateEvent(NULL, fManualReset, FALSE, NULL); if (NULL == m_hEvent) { if (NULL != phr && SUCCEEDED(*phr)) { *phr = E_OUTOFMEMORY; } } } CAMEvent::CAMEvent(__inout_opt HRESULT *phr) { m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (NULL == m_hEvent) { if (NULL != phr && SUCCEEDED(*phr)) { *phr = E_OUTOFMEMORY; } } } CAMEvent::~CAMEvent() { if (m_hEvent) { EXECUTE_ASSERT(CloseHandle(m_hEvent)); } } // --- CAMMsgEvent ----------------------- // One routine. The rest is handled in CAMEvent CAMMsgEvent::CAMMsgEvent(__inout_opt HRESULT *phr) : CAMEvent(FALSE, phr) { } BOOL CAMMsgEvent::WaitMsg(DWORD dwTimeout) { // wait for the event to be signalled, or for the // timeout (in MS) to expire. allow SENT messages // to be processed while we wait DWORD dwWait; DWORD dwStartTime = 0; // set the waiting period. DWORD dwWaitTime = dwTimeout; // the timeout will eventually run down as we iterate // processing messages. grab the start time so that // we can calculate elapsed times. if (dwWaitTime != INFINITE) { dwStartTime = timeGetTime(); } do { dwWait = MsgWaitForMultipleObjects(1,&m_hEvent,FALSE, dwWaitTime, QS_SENDMESSAGE); if (dwWait == WAIT_OBJECT_0 + 1) { MSG Message; PeekMessage(&Message,NULL,0,0,PM_NOREMOVE); // If we have an explicit length of time to wait calculate // the next wake up point - which might be now. // If dwTimeout is INFINITE, it stays INFINITE if (dwWaitTime != INFINITE) { DWORD dwElapsed = timeGetTime()-dwStartTime; dwWaitTime = (dwElapsed >= dwTimeout) ? 0 // wake up with WAIT_TIMEOUT : dwTimeout-dwElapsed; } } } while (dwWait == WAIT_OBJECT_0 + 1); // return TRUE if we woke on the event handle, // FALSE if we timed out. return (dwWait == WAIT_OBJECT_0); } // --- CAMThread ---------------------- CAMThread::CAMThread(__inout_opt HRESULT *phr) : m_EventSend(TRUE, phr), // must be manual-reset for CheckRequest() m_EventComplete(FALSE, phr) { m_hThread = NULL; } CAMThread::~CAMThread() { Close(); } // when the thread starts, it calls this function. We unwrap the 'this' //pointer and call ThreadProc. DWORD WINAPI CAMThread::InitialThreadProc(__inout LPVOID pv) { HRESULT hrCoInit = CAMThread::CoInitializeHelper(); if(FAILED(hrCoInit)) { DbgLog((LOG_ERROR, 1, TEXT("CoInitializeEx failed."))); } CAMThread * pThread = (CAMThread *) pv; HRESULT hr = pThread->ThreadProc(); if(SUCCEEDED(hrCoInit)) { CoUninitialize(); } return hr; } BOOL CAMThread::Create() { DWORD threadid; CAutoLock lock(&m_AccessLock); if (ThreadExists()) { return FALSE; } m_hThread = CreateThread( NULL, 0, CAMThread::InitialThreadProc, this, 0, &threadid); if (!m_hThread) { return FALSE; } return TRUE; } DWORD CAMThread::CallWorker(DWORD dwParam) { // lock access to the worker thread for scope of this object CAutoLock lock(&m_AccessLock); if (!ThreadExists()) { return (DWORD) E_FAIL; } // set the parameter m_dwParam = dwParam; // signal the worker thread m_EventSend.Set(); // wait for the completion to be signalled m_EventComplete.Wait(); // done - this is the thread's return value return m_dwReturnVal; } // Wait for a request from the client DWORD CAMThread::GetRequest() { m_EventSend.Wait(); return m_dwParam; } // is there a request? BOOL CAMThread::CheckRequest(__out_opt DWORD * pParam) { if (!m_EventSend.Check()) { return FALSE; } else { if (pParam) { *pParam = m_dwParam; } return TRUE; } } // reply to the request void CAMThread::Reply(DWORD dw) { m_dwReturnVal = dw; // The request is now complete so CheckRequest should fail from // now on // // This event should be reset BEFORE we signal the client or // the client may Set it before we reset it and we'll then // reset it (!) m_EventSend.Reset(); // Tell the client we're finished m_EventComplete.Set(); } HRESULT CAMThread::CoInitializeHelper() { // call CoInitializeEx and tell OLE not to create a window (this // thread probably won't dispatch messages and will hang on // broadcast msgs o/w). // // If CoInitEx is not available, threads that don't call CoCreate // aren't affected. Threads that do will have to handle the // failure. Perhaps we should fall back to CoInitialize and risk // hanging? // // older versions of ole32.dll don't have CoInitializeEx HRESULT hr = E_FAIL; HINSTANCE hOle = GetModuleHandle(TEXT("ole32.dll")); if(hOle) { typedef HRESULT (STDAPICALLTYPE *PCoInitializeEx)( LPVOID pvReserved, DWORD dwCoInit); PCoInitializeEx pCoInitializeEx = (PCoInitializeEx)(GetProcAddress(hOle, "CoInitializeEx")); if(pCoInitializeEx) { hr = (*pCoInitializeEx)(0, COINIT_DISABLE_OLE1DDE ); } } else { // caller must load ole32.dll DbgBreak("couldn't locate ole32.dll"); } return hr; } // destructor for CMsgThread - cleans up any messages left in the // queue when the thread exited CMsgThread::~CMsgThread() { if (m_hThread != NULL) { WaitForSingleObject(m_hThread, INFINITE); EXECUTE_ASSERT(CloseHandle(m_hThread)); } POSITION pos = m_ThreadQueue.GetHeadPosition(); while (pos) { CMsg * pMsg = m_ThreadQueue.GetNext(pos); delete pMsg; } m_ThreadQueue.RemoveAll(); if (m_hSem != NULL) { EXECUTE_ASSERT(CloseHandle(m_hSem)); } } BOOL CMsgThread::CreateThread( ) { m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); if (m_hSem == NULL) { return FALSE; } m_hThread = ::CreateThread(NULL, 0, DefaultThreadProc, (LPVOID)this, 0, &m_ThreadId); return m_hThread != NULL; } // This is the threads message pump. Here we get and dispatch messages to // clients thread proc until the client refuses to process a message. // The client returns a non-zero value to stop the message pump, this // value becomes the threads exit code. DWORD WINAPI CMsgThread::DefaultThreadProc( __inout LPVOID lpParam ) { CMsgThread *lpThis = (CMsgThread *)lpParam; CMsg msg; LRESULT lResult; // !!! CoInitialize(NULL); // allow a derived class to handle thread startup lpThis->OnThreadInit(); do { lpThis->GetThreadMsg(&msg); lResult = lpThis->ThreadMessageProc(msg.uMsg,msg.dwFlags, msg.lpParam, msg.pEvent); } while (lResult == 0L); // !!! CoUninitialize(); return (DWORD)lResult; } // Block until the next message is placed on the list m_ThreadQueue. // copies the message to the message pointed to by *pmsg void CMsgThread::GetThreadMsg(__out CMsg *msg) { CMsg * pmsg = NULL; // keep trying until a message appears while (TRUE) { { CAutoLock lck(&m_Lock); pmsg = m_ThreadQueue.RemoveHead(); if (pmsg == NULL) { m_lWaiting++; } else { break; } } // the semaphore will be signalled when it is non-empty WaitForSingleObject(m_hSem, INFINITE); } // copy fields to caller's CMsg *msg = *pmsg; // this CMsg was allocated by the 'new' in PutThreadMsg delete pmsg; } // Helper function - convert int to WSTR void WINAPI IntToWstr(int i, __out_ecount(12) LPWSTR wstr) { #ifdef UNICODE if (FAILED(StringCchPrintf(wstr, 12, L"%d", i))) { wstr[0] = 0; } #else TCHAR temp[12]; if (FAILED(StringCchPrintf(temp, NUMELMS(temp), "%d", i))) { wstr[0] = 0; } else { MultiByteToWideChar(CP_ACP, 0, temp, -1, wstr, 12); } #endif } // IntToWstr #define MEMORY_ALIGNMENT 4 #define MEMORY_ALIGNMENT_LOG2 2 #define MEMORY_ALIGNMENT_MASK MEMORY_ALIGNMENT - 1 void * __stdcall memmoveInternal(void * dst, const void * src, size_t count) { void * ret = dst; #ifdef _X86_ if (dst <= src || (char *)dst >= ((char *)src + count)) { /* * Non-Overlapping Buffers * copy from lower addresses to higher addresses */ _asm { mov esi,src mov edi,dst mov ecx,count cld mov edx,ecx and edx,MEMORY_ALIGNMENT_MASK shr ecx,MEMORY_ALIGNMENT_LOG2 rep movsd or ecx,edx jz memmove_done rep movsb memmove_done: } } else { /* * Overlapping Buffers * copy from higher addresses to lower addresses */ _asm { mov esi,src mov edi,dst mov ecx,count std add esi,ecx add edi,ecx dec esi dec edi rep movsb cld } } #else MoveMemory(dst, src, count); #endif return ret; } HRESULT AMSafeMemMoveOffset( __in_bcount(dst_size) void * dst, __in size_t dst_size, __in DWORD cb_dst_offset, __in_bcount(src_size) const void * src, __in size_t src_size, __in DWORD cb_src_offset, __in size_t count) { // prevent read overruns if( count + cb_src_offset < count || // prevent integer overflow count + cb_src_offset > src_size) // prevent read overrun { return E_INVALIDARG; } // prevent write overruns if( count + cb_dst_offset < count || // prevent integer overflow count + cb_dst_offset > dst_size) // prevent write overrun { return E_INVALIDARG; } memmoveInternal( (BYTE *)dst+cb_dst_offset, (BYTE *)src+cb_src_offset, count); return S_OK; } #ifdef DEBUG /******************************Public*Routine******************************\ * Debug CCritSec helpers * * We provide debug versions of the Constructor, destructor, Lock and Unlock * routines. The debug code tracks who owns each critical section by * maintaining a depth count. * * History: * \**************************************************************************/ CCritSec::CCritSec() { InitializeCriticalSection(&m_CritSec); m_currentOwner = m_lockCount = 0; m_fTrace = FALSE; } CCritSec::~CCritSec() { DeleteCriticalSection(&m_CritSec); } void CCritSec::Lock() { UINT tracelevel=3; DWORD us = GetCurrentThreadId(); DWORD currentOwner = m_currentOwner; if (currentOwner && (currentOwner != us)) { // already owned, but not by us if (m_fTrace) { DbgLog((LOG_LOCKING, 2, TEXT("Thread %d about to wait for lock %x owned by %d"), GetCurrentThreadId(), &m_CritSec, currentOwner)); tracelevel=2; // if we saw the message about waiting for the critical // section we ensure we see the message when we get the // critical section } } EnterCriticalSection(&m_CritSec); if (0 == m_lockCount++) { // we now own it for the first time. Set owner information m_currentOwner = us; if (m_fTrace) { DbgLog((LOG_LOCKING, tracelevel, TEXT("Thread %d now owns lock %x"), m_currentOwner, &m_CritSec)); } } } void CCritSec::Unlock() { if (0 == --m_lockCount) { // about to be unowned if (m_fTrace) { DbgLog((LOG_LOCKING, 3, TEXT("Thread %d releasing lock %x"), m_currentOwner, &m_CritSec)); } m_currentOwner = 0; } LeaveCriticalSection(&m_CritSec); } void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace) { pcCrit->m_fTrace = fTrace; } BOOL WINAPI CritCheckIn(CCritSec * pcCrit) { return (GetCurrentThreadId() == pcCrit->m_currentOwner); } BOOL WINAPI CritCheckIn(const CCritSec * pcCrit) { return (GetCurrentThreadId() == pcCrit->m_currentOwner); } BOOL WINAPI CritCheckOut(CCritSec * pcCrit) { return (GetCurrentThreadId() != pcCrit->m_currentOwner); } BOOL WINAPI CritCheckOut(const CCritSec * pcCrit) { return (GetCurrentThreadId() != pcCrit->m_currentOwner); } #endif STDAPI WriteBSTR(__deref_out BSTR *pstrDest, LPCWSTR szSrc) { *pstrDest = SysAllocString( szSrc ); if( !(*pstrDest) ) return E_OUTOFMEMORY; return NOERROR; } STDAPI FreeBSTR(__deref_in BSTR* pstr) { if( (PVOID)*pstr == NULL ) return S_FALSE; SysFreeString( *pstr ); return NOERROR; } // Return a wide string - allocating memory for it // Returns: // S_OK - no error // E_POINTER - ppszReturn == NULL // E_OUTOFMEMORY - can't allocate memory for returned string STDAPI AMGetWideString(LPCWSTR psz, __deref_out LPWSTR *ppszReturn) { CheckPointer(ppszReturn, E_POINTER); ValidateReadWritePtr(ppszReturn, sizeof(LPWSTR)); *ppszReturn = NULL; size_t nameLen; HRESULT hr = StringCbLengthW(psz, 100000, &nameLen); if (FAILED(hr)) { return hr; } *ppszReturn = (LPWSTR)CoTaskMemAlloc(nameLen + sizeof(WCHAR)); if (*ppszReturn == NULL) { return E_OUTOFMEMORY; } CopyMemory(*ppszReturn, psz, nameLen + sizeof(WCHAR)); return NOERROR; } // Waits for the HANDLE hObject. While waiting messages sent // to windows on our thread by SendMessage will be processed. // Using this function to do waits and mutual exclusion // avoids some deadlocks in objects with windows. // Return codes are the same as for WaitForSingleObject DWORD WINAPI WaitDispatchingMessages( HANDLE hObject, DWORD dwWait, HWND hwnd, UINT uMsg, HANDLE hEvent) { BOOL bPeeked = FALSE; DWORD dwResult; DWORD dwStart = 0; DWORD dwThreadPriority = THREAD_PRIORITY_HIGHEST; static UINT uMsgId = 0; HANDLE hObjects[2] = { hObject, hEvent }; if (dwWait != INFINITE && dwWait != 0) { dwStart = GetTickCount(); } for (; ; ) { DWORD nCount = NULL != hEvent ? 2 : 1; // Minimize the chance of actually dispatching any messages // by seeing if we can lock immediately. dwResult = WaitForMultipleObjects(nCount, hObjects, FALSE, 0); if (dwResult < WAIT_OBJECT_0 + nCount) { break; } DWORD dwTimeOut = dwWait; if (dwTimeOut > 10) { dwTimeOut = 10; } dwResult = MsgWaitForMultipleObjects( nCount, hObjects, FALSE, dwTimeOut, hwnd == NULL ? QS_SENDMESSAGE : QS_SENDMESSAGE + QS_POSTMESSAGE); if (dwResult == WAIT_OBJECT_0 + nCount || dwResult == WAIT_TIMEOUT && dwTimeOut != dwWait) { MSG msg; if (hwnd != NULL) { while (PeekMessage(&msg, hwnd, uMsg, uMsg, PM_REMOVE)) { DispatchMessage(&msg); } } // Do this anyway - the previous peek doesn't flush out the // messages PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); if (dwWait != INFINITE && dwWait != 0) { DWORD dwNow = GetTickCount(); // Working with differences handles wrap-around DWORD dwDiff = dwNow - dwStart; if (dwDiff > dwWait) { dwWait = 0; } else { dwWait -= dwDiff; } dwStart = dwNow; } if (!bPeeked) { // Raise our priority to prevent our message queue // building up dwThreadPriority = GetThreadPriority(GetCurrentThread()); if (dwThreadPriority < THREAD_PRIORITY_HIGHEST) { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); } bPeeked = TRUE; } } else { break; } } if (bPeeked) { SetThreadPriority(GetCurrentThread(), dwThreadPriority); if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) { if (uMsgId == 0) { uMsgId = RegisterWindowMessage(TEXT("AMUnblock")); } if (uMsgId != 0) { MSG msg; // Remove old ones while (PeekMessage(&msg, (HWND)-1, uMsgId, uMsgId, PM_REMOVE)) { } } PostThreadMessage(GetCurrentThreadId(), uMsgId, 0, 0); } } return dwResult; } HRESULT AmGetLastErrorToHResult() { DWORD dwLastError = GetLastError(); if(dwLastError != 0) { return HRESULT_FROM_WIN32(dwLastError); } else { return E_FAIL; } } IUnknown* QzAtlComPtrAssign(__deref_inout_opt IUnknown** pp, __in_opt IUnknown* lp) { if (lp != NULL) lp->AddRef(); if (*pp) (*pp)->Release(); *pp = lp; return lp; } /****************************************************************************** CompatibleTimeSetEvent CompatibleTimeSetEvent() sets the TIME_KILL_SYNCHRONOUS flag before calling timeSetEvent() if the current operating system supports it. TIME_KILL_SYNCHRONOUS is supported on Windows XP and later operating systems. Parameters: - The same parameters as timeSetEvent(). See timeSetEvent()'s documentation in the Platform SDK for more information. Return Value: - The same return value as timeSetEvent(). See timeSetEvent()'s documentation in the Platform SDK for more information. ******************************************************************************/ MMRESULT CompatibleTimeSetEvent( UINT uDelay, UINT uResolution, __in LPTIMECALLBACK lpTimeProc, DWORD_PTR dwUser, UINT fuEvent ) { #if WINVER >= 0x0501 { static bool fCheckedVersion = false; static bool fTimeKillSynchronousFlagAvailable = false; if( !fCheckedVersion ) { fTimeKillSynchronousFlagAvailable = TimeKillSynchronousFlagAvailable(); fCheckedVersion = true; } if( fTimeKillSynchronousFlagAvailable ) { fuEvent = fuEvent | TIME_KILL_SYNCHRONOUS; } } #endif // WINVER >= 0x0501 return timeSetEvent( uDelay, uResolution, lpTimeProc, dwUser, fuEvent ); } bool TimeKillSynchronousFlagAvailable( void ) { OSVERSIONINFO osverinfo; osverinfo.dwOSVersionInfoSize = sizeof(osverinfo); if( GetVersionEx( &osverinfo ) ) { // Windows XP's major version is 5 and its' minor version is 1. // timeSetEvent() started supporting the TIME_KILL_SYNCHRONOUS flag // in Windows XP. if( (osverinfo.dwMajorVersion > 5) || ( (osverinfo.dwMajorVersion == 5) && (osverinfo.dwMinorVersion >= 1) ) ) { return true; } } return false; } #endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */