Click here to Skip to main content
15,884,472 members
Articles / Desktop Programming / MFC

CDirectoryChangeWatcher - ReadDirectoryChangesW all wrapped up

Rate me:
Please Sign up or sign in to vote.
4.92/5 (114 votes)
11 May 2002 2.1M   39.4K   365  
This class wraps up ReadDirectoryChangesW.
// DelayedDirectoryChangeHandler.cpp: implementation of the CDelayedDirectoryChangeHandler2 class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DirectoryChanges.h"
#include "DelayedDirectoryChangeHandler.h"
#include <process.h>//for _beginthreadex

#include <shlwapi.h>				 // for PathMatchSpec
#pragma comment( lib, "shlwapi.lib") // function


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#define UWM_DELAYED_DIRECTORY_NOTIFICATION (WM_APP+1024)


HINSTANCE GetInstanceHandle()
{
	return (HINSTANCE)GetModuleHandle(NULL);
	// ASSERT( AfxGetInstanceHandle() == (HINSTANCE)GetModuleHandle(NULL) ); <-- true for building .exe's 
	//NOTE: In Dll's using shared MFC, AfxGetInstanceHandle() != (HINSTANCE)GetModuleHandle(NULL)...
	//don't know if this is the case for dll's using static MFC
}
static inline bool IsEmptyString(LPCTSTR sz)
{
	return (bool)(sz==NULL || *sz == 0);
}
/*********************************************************
  PathMatchSpec() requires IE 4.0 or greater on NT...
  if running on NT 4.0 w/ out IE 4.0, then uses this function instead.

  Based on code by Jack Handy:
  http://www.codeproject.com/string/wildcmp.asp

  Changed slightly to match the PathMatchSpec signature, be unicode compliant & to ignore case by myself.
  
*********************************************************/

#define _TESTING_WILDCMP_ONLY_ 

BOOL STDAPICALLTYPE wildcmp(LPCTSTR string, LPCTSTR wild ) 
{
	const TCHAR *cp, *mp;
	cp = mp = NULL;
	
	while ((*string) && (*wild != _T('*'))) 
	{
		if ((_toupper(*wild) != _toupper(*string)) && (*wild != _T('?'))) 
		{
			return FALSE;
		}
		wild++;
		string++;
	}
		
	while (*string) 
	{
		if (*wild == _T('*')) 
		{
			if (!*++wild) 
			{
				return TRUE;
			}
			mp = wild;
			cp = string+1;
		} 
		else 
		if ((_toupper(*wild) == _toupper(*string)) || (*wild == _T('?'))) 
		{
			wild++;
			string++;
		} 
		else 
		{
			wild = mp;
			string = cp++;
		}
	}
		
	while (*wild == _T('*')) 
	{
		wild++;
	}
	return (!*wild)? TRUE : FALSE;
}

//////////////////////////////////////////////////////////////////////////
//
//CDirChangeNotification member functions:
//
CDirChangeNotification::CDirChangeNotification(CDelayedDirectoryChangeHandler *	pDelayedHandler, DWORD dwPartialPathOffset)
:m_pDelayedHandler( pDelayedHandler )
,m_szFileName1(NULL)
,m_szFileName2(NULL)
,m_dwError(0UL)
,m_dwPartialPathOffset(dwPartialPathOffset)
{
	ASSERT( pDelayedHandler );
}

CDirChangeNotification::~CDirChangeNotification()
{
	if( m_szFileName1 ) free(m_szFileName1), m_szFileName1 = NULL;
	if( m_szFileName2 ) free(m_szFileName2), m_szFileName2 = NULL;
}

void CDirChangeNotification::DispatchNotificationFunction()
{
	ASSERT( m_pDelayedHandler );
	if( m_pDelayedHandler )
		m_pDelayedHandler->DispatchNotificationFunction( this );
}

void CDirChangeNotification::PostOn_FileAdded(LPCTSTR szFileName)
{
	ASSERT( szFileName );
	m_eFunctionToDispatch	= eOn_FileAdded;
	m_szFileName1			= _tcsdup( szFileName) ;
	//
	// post the message so it'll be dispatch by another thread.
	PostNotification();

}
void CDirChangeNotification::PostOn_FileRemoved(LPCTSTR szFileName)
{
	ASSERT( szFileName );
	m_eFunctionToDispatch	= eOn_FileRemoved;
	m_szFileName1			= _tcsdup( szFileName) ;
	//
	// post the message so it'll be dispatched by another thread.
	PostNotification();
	
}
void CDirChangeNotification::PostOn_FileNameChanged(LPCTSTR szOldName, LPCTSTR szNewName)
{
	ASSERT( szOldName && szNewName );

	m_eFunctionToDispatch	= eOn_FileNameChanged;
	m_szFileName1			= _tcsdup( szOldName) ;
	m_szFileName2			= _tcsdup( szNewName) ;
	//
	// post the message so it'll be dispatched by another thread.
	PostNotification();
	
}

void CDirChangeNotification::PostOn_FileModified(LPCTSTR szFileName)
{
	ASSERT( szFileName );

	m_eFunctionToDispatch	= eOn_FileModified;
	m_szFileName1			= _tcsdup( szFileName );
	//
	// post the message so it'll be dispatched by another thread.
	PostNotification();
}

void CDirChangeNotification::PostOn_ReadDirectoryChangesError(DWORD dwError, LPCTSTR szDirectoryName)
{
	ASSERT( szDirectoryName );

	m_eFunctionToDispatch = eOn_ReadDirectoryChangesError;
	m_dwError			  = dwError;
	m_szFileName1		  = _tcsdup(szDirectoryName);
	//
	// post the message so it'll be dispatched by the another thread.
	PostNotification();
	
}

void CDirChangeNotification::PostOn_WatchStarted(DWORD dwError, LPCTSTR szDirectoryName)
{
	ASSERT( szDirectoryName );

	m_eFunctionToDispatch = eOn_WatchStarted;
	m_dwError			  =	dwError;
	m_szFileName1		  = _tcsdup(szDirectoryName);

	PostNotification();
}

void CDirChangeNotification::PostOn_WatchStopped(LPCTSTR szDirectoryName)
{
	ASSERT( szDirectoryName );

	m_eFunctionToDispatch = eOn_WatchStopped;
	m_szFileName1		  = _tcsdup(szDirectoryName);

	PostNotification();
}

void CDirChangeNotification::PostNotification()
{
	ASSERT( m_pDelayedHandler );
	if( m_pDelayedHandler )
		m_pDelayedHandler->PostNotification( this );
}

static LRESULT CALLBACK DelayedNotificationWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
//
//	This is the wndproc for the notification window
//
//	it's here to dispatch the notifications to the client
//
{
	if( message == UWM_DELAYED_DIRECTORY_NOTIFICATION )
	{
		CDirChangeNotification * pNotification = reinterpret_cast<CDirChangeNotification*>(lParam);
		ASSERT(  pNotification );
		if( pNotification )
		{
			DWORD dwEx(0);
			__try{
				pNotification->DispatchNotificationFunction();
			}
			__except(dwEx = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER){
				//An exception was raised:
				//
				//	Likely cause: there was a problem creating the CDelayedDirectoryChangeHandler::m_hWatchStoppedDispatchedEvent object
				//	and the change handler object was deleted before the notification could be dispatched to this function.
				//
				//  or perhaps, somebody's implementation of an overridden function caused an exception
				TRACE(_T("Following exception occurred: %d -- File: %s Line: %d\n"), dwEx, _T(__FILE__), __LINE__);
			}
		}
		
		return 0UL;
	}
	else
		return DefWindowProc(hWnd,message,wParam,lParam);
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
//
//CDelayedNotificationWindow static member vars:
//
long CDelayedNotificationWindow::s_nRefCnt = 0L;
HWND CDelayedNotificationWindow::s_hWnd = NULL;
BOOL CDelayedNotificationWindow::s_bRegisterWindow = FALSE;
//
//
long CDelayedNotificationWindow::AddRef()//creates window for first time if necessary
{
	if( InterlockedIncrement(&s_nRefCnt) == 1
		||	!::IsWindow( s_hWnd ) )
	{
		TRACE(_T("CDelayedNotificationWindow -- Creating the notification window\n"));
		VERIFY( CreateNotificationWindow() );
	}
	return s_nRefCnt;
}

long CDelayedNotificationWindow::Release()//destroys window for last time if necessary
{
	long nRefCnt = -1;
	if( (nRefCnt = InterlockedDecrement(&s_nRefCnt)) == 0 )
	{
		//no body else using the window so destroy it?
		TRACE(_T("CDelayedNotificationWindow -- Destroying the notification window\n"));
		DestroyWindow( s_hWnd );
		s_hWnd = NULL;
	}
	return nRefCnt;
}
BOOL CDelayedNotificationWindow::RegisterWindowClass(LPCTSTR szClassName)
//
//	registers our own window class to use as the hidden notification window.
//
{
	WNDCLASS wc = {0};
	
	wc.style = 0;
	wc.hInstance		= GetInstanceHandle();
	wc.lpszClassName	= szClassName;
	wc.hbrBackground	= (HBRUSH)GetStockObject( WHITE_BRUSH );
	wc.lpfnWndProc		= DelayedNotificationWndProc;
	
	ATOM ant = RegisterClass( &wc );
	if( ant == NULL )
	{
		TRACE(_T("CDirChangeNotification::RegisterWindowClass - RegisterClass failed: %d\n"), GetLastError());
	}
	return (BOOL)(ant!= NULL);
	
}

BOOL CDelayedNotificationWindow::CreateNotificationWindow()
//
//	Create the hidden notification windows.
//
{
	TCHAR szClassName[] = _T("Delayed_Message_Sender");
	if( !s_bRegisterWindow )
		s_bRegisterWindow = RegisterWindowClass(szClassName);
	s_hWnd 	= CreateWindowEx(0, szClassName, _T("DelayedWnd"),0,0,0,0,0, NULL, 0, 
							GetInstanceHandle(), NULL);
	if( s_hWnd == NULL )
	{
		TRACE(_T("Unable to create notification window! GetLastError(): %d\n"), GetLastError());
		TRACE(_T("File: %s Line: %d\n"), _T(__FILE__), __LINE__);
	}
	
	return (BOOL)(s_hWnd != NULL);
}
void CDelayedNotificationWindow::PostNotification(CDirChangeNotification * pNotification)
//
//	Posts a message to a window created in the main 
//	thread.
//	The main thread catches this message, and dispatches it in 
//	the context of the main thread.
//
{
	ASSERT( pNotification );
	ASSERT( s_hWnd );
	ASSERT( ::IsWindow( s_hWnd ) );

	PostMessage(s_hWnd, 
				UWM_DELAYED_DIRECTORY_NOTIFICATION, 
				0, 
				reinterpret_cast<LPARAM>( pNotification ));

//  if you don't want the notification delayed, 
//  
//	if( false )
//	{
//		pNotification->DispatchNotificationFunction();
//	}
}

/////////////////////////////////////////////////////////
//	CDelayedNoticationThread
//
long	CDelayedNotificationThread::s_nRefCnt = 0L;
HANDLE	CDelayedNotificationThread::s_hThread = NULL;
DWORD	CDelayedNotificationThread::s_dwThreadID = 0UL;

void CDelayedNotificationThread::PostNotification(CDirChangeNotification * pNotification)
{
	ASSERT( s_hThread != NULL );
	ASSERT( s_dwThreadID != 0 );

	if(
		!PostThreadMessage(s_dwThreadID, 
						   UWM_DELAYED_DIRECTORY_NOTIFICATION, 
						   0, 
						   reinterpret_cast<LPARAM>(pNotification))
	  )
	{
		//Note, this can sometimes fail.
		//Will fail if: s_dwThreadID references a invalid thread id(the thread has died for example)
		// OR will fail if the thread doesn't have a message queue.
		//
		//	This was failing because the thread had not been fully started by the time PostThreadMessage had been called
		//
		//Note: if this fails, it creates a memory leak because
		//the CDirChangeNotification object that was allocated and posted
		//to the thread is actually never going to be dispatched and then deleted.... it's
		//hanging in limbo.....

		//
		//	The fix for this situation was to force the thread that starts
		//	this worker thread to wait until the worker thread was fully started before
		//	continueing.  accomplished w/ an event... also.. posting a message to itself before signalling the 
		//  'spawning' thread that it was started ensured that there was a message pump
		//  associated w/ the worker thread by the time PostThreadMessage was called.
		TRACE(_T("PostThreadMessage() failed while posting to thread id: %d! GetLastError(): %d%s\n"), s_dwThreadID, GetLastError(), GetLastError() == ERROR_INVALID_THREAD_ID? _T("(ERROR_INVALID_THREAD_ID)") : _T(""));
	}
}

bool CDelayedNotificationThread::StartThread()
{
	TRACE(_T("CDelayedNotificationThread::StartThread()\n"));
	ASSERT( s_hThread == NULL 
		&&	s_dwThreadID == 0 );
	s_hThread = (HANDLE)_beginthreadex(NULL,0, 
								ThreadFunc, this, 0, (UINT*) &s_dwThreadID);
	if( s_hThread )
		WaitForThreadStartup();

	return s_hThread == NULL ? false : true;

}

bool CDelayedNotificationThread::StopThread()
{
	TRACE(_T("CDelayedNotificationThread::StopThread()\n"));
	if( s_hThread != NULL 
	&&	s_dwThreadID != 0 )
	{
		PostThreadMessage(s_dwThreadID, WM_QUIT, 0,0);

		WaitForSingleObject(s_hThread, INFINITE);
		CloseHandle(s_hThread);
		s_hThread	 = NULL;
		s_dwThreadID = 0UL;
		return true;
	}
	return true;//already shutdown
}

UINT __stdcall CDelayedNotificationThread::ThreadFunc(LPVOID lpvThis)
{
	//UNREFERENCED_PARAMETER( lpvThis );
	//
	//	Implements a simple message pump
	//
	CDelayedNotificationThread * pThis = reinterpret_cast<CDelayedNotificationThread*>(lpvThis);
	ASSERT( pThis );

	//
	//	Insure that this thread has a message queue by the time another
	//	thread gets control and tries to use PostThreadMessage
	//	problems can happen if someone tries to use PostThreadMessage
	//	in between the time pThis->SignalThreadStartup() is called,
	//	and the first call to GetMessage();

	::PostMessage(NULL, WM_NULL, 0,0);//if this thread didn't have a message queue before this, it does now.


	//
	//
	//	Signal that this thread has started so that StartThread can continue.
	//
	if( pThis ) pThis->SignalThreadStartup();

	TRACE(_T("CDelayedNotificationThread::ThreadFunc() ThreadID: %d -- Starting\n"), GetCurrentThreadId());
	MSG msg;
	do{
		while( GetMessage(&msg, NULL, 0,0) )//note GetMessage() can return -1, but only if i give it a bad HWND.(HWND for another thread for example)..i'm not giving an HWND, so no problemo here.
		{
			if( msg.message == UWM_DELAYED_DIRECTORY_NOTIFICATION )
			{
				CDirChangeNotification * pNotification = 
								reinterpret_cast<CDirChangeNotification *>( msg.lParam );
				DWORD dwEx(0UL);

				__try{
				if( pNotification )
					pNotification->DispatchNotificationFunction();
				}
				__except(dwEx = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER){
				//An exception was raised:
				//
				//	Likely causes: 
				//		* There was a problem creating the CDelayedDirectoryChangeHandler::m_hWatchStoppedDispatchedEvent object
				//			and the change handler object was deleted before the notification could be dispatched to this function.
				//
				//		* Somebody's implementation of an overridden virtual function caused an exception
				TRACE(_T("The following exception occurred: %d -- File: %s Line: %d\n"), dwEx, _T(__FILE__), __LINE__);
				}
			}
			else
			if( msg.message == WM_QUIT )
			{
				break;
			}
		}
	}while( msg.message != WM_QUIT );
	TRACE(_T("CDelayedNotificationThread::ThreadFunc() exiting. ThreadID: %d\n"), GetCurrentThreadId());
	return 0;
}

long CDelayedNotificationThread::AddRef()
{
	if( InterlockedIncrement(&s_nRefCnt) == 1 )
	{
		VERIFY( StartThread() );
	}
	return s_nRefCnt;
}
long CDelayedNotificationThread::Release()
{
	if( InterlockedDecrement(&s_nRefCnt) <= 0 )
	{
		s_nRefCnt = 0;
		VERIFY( StopThread() );
	}
	return s_nRefCnt;
}

///////////////////////////////////////////////////////
//static member data for CDelayedDirectoryChangeHandler
HINSTANCE CDelayedDirectoryChangeHandler::s_hShlwapi_dll = NULL;//for the PathMatchSpec() function
BOOL CDelayedDirectoryChangeHandler::s_bShlwapi_dllExists = TRUE;
long CDelayedDirectoryChangeHandler::s_nRefCnt_hShlwapi = 0L;
FUNC_PatternMatchSpec CDelayedDirectoryChangeHandler::s_fpPatternMatchSpec = wildcmp;//default
///////////////////////////////////////////////////////
//construction destruction
CDelayedDirectoryChangeHandler::CDelayedDirectoryChangeHandler(CDirectoryChangeHandler * pRealHandler, bool bAppHasGUI, LPCTSTR szIncludeFilter, LPCTSTR szExcludeFilter, DWORD dwFilterFlags)
: m_pDelayNotifier( NULL )
 ,m_pRealHandler( pRealHandler )
 ,m_szIncludeFilter(NULL)
 ,m_szExcludeFilter(NULL)
 ,m_dwFilterFlags( dwFilterFlags )
 ,m_dwPartialPathOffset( 0UL )
 ,m_hWatchStoppedDispatchedEvent(NULL)
 ,m_nNumIncludeFilterSpecs(0)
 ,m_nNumExcludeFilterSpecs(0)
{


	ASSERT( m_pRealHandler ); 

	InitializePathMatchFunc( szIncludeFilter, szExcludeFilter );

	//
	// See that we're 
	//


	m_hWatchStoppedDispatchedEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);//AUTO-RESET, not initially signalled
	ASSERT( m_hWatchStoppedDispatchedEvent );
	
	if( bAppHasGUI )
	{
		//
		//	The value true was passed to the CDirectoryChangeWatcher constructor.
		//	It's assumed that your app has a gui, that is, it implements
		//	a message pump.  To delay the notification to another thread,
		//	we'll use a hidden notification window.
		//
		m_pDelayNotifier = new CDelayedNotificationWindow();
	}
	else
	{
		// The value 'false' was passed to the CDirectoryChangeWatcher constructor.
		//
		// Your app has no message pump... use a class that implements one for you
		// in a worker thread.
		//
		// Notifications will be executed in this worker thread.
		//
		m_pDelayNotifier = new CDelayedNotificationThread();
	}
}

CDelayedDirectoryChangeHandler::~CDelayedDirectoryChangeHandler()
{
	if( m_pRealHandler )
		delete m_pRealHandler, m_pRealHandler = NULL;
	if( m_pDelayNotifier )
		delete m_pDelayNotifier, m_pDelayNotifier = NULL;

	if( m_hWatchStoppedDispatchedEvent )
		CloseHandle(m_hWatchStoppedDispatchedEvent), m_hWatchStoppedDispatchedEvent = NULL;

	if( m_szIncludeFilter ){
		if( m_nNumIncludeFilterSpecs == 1 )
			free(m_szIncludeFilter);
		else
		{
			TCHAR ** ppTmp = (TCHAR**)m_szIncludeFilter;
			for(int i(0); i < m_nNumIncludeFilterSpecs; ++i)
			{
				free( *ppTmp++ );
			}
			free( m_szIncludeFilter );
		}
		m_szIncludeFilter = NULL;
		m_nNumIncludeFilterSpecs;
	}
	if( m_szExcludeFilter ) {
		if( m_nNumExcludeFilterSpecs == 1 )
			free(m_szExcludeFilter);
		else{
			TCHAR ** ppTmp = (TCHAR**)m_szExcludeFilter;
			for(int i(0); i < m_nNumExcludeFilterSpecs; ++i)
			{
				free( *ppTmp++ );
			}
			free( m_szExcludeFilter );
		}
		m_szExcludeFilter = NULL;
		m_nNumExcludeFilterSpecs = 0;
	}

	UninitializePathMatchFunc();
}

BOOL CDelayedDirectoryChangeHandler::_PathMatchSpec(LPCTSTR szPath, LPCTSTR szPattern)
{
	if( s_fpPatternMatchSpec )
	{
		return s_fpPatternMatchSpec(szPath, szPattern);
	}
	ASSERT( FALSE );
	return TRUE;//everything matches.
}

BOOL CDelayedDirectoryChangeHandler::InitializePathMatchFunc(LPCTSTR szIncludeFilter, LPCTSTR szExcludeFilter)
//
//
//	To support the Include and Exclude filters, the PathMatchSpec function is used.
//	PathMatchSpec is only supported on NT4.0 if IE 4.0 is installed.
//
//	for the case where this code is running on NT 4.0 w/out IE 4.0, we use
//	a different function: wildcmp ()
//
//
//	This function attempts to load shlwapi.dll dynamically and find the PathMatchSpec function.
//
//	if the function PathMatchSpec can't be found, the function pointer s_fpPathMatchSpec is set to wildcmp.
//
//
//	Note:  wildcmp doesn't support multiple file specs separated by a semi-colon
//	as PathMatchSpec does.... we'll support it by parsing them 
//	when we want to test the filters, we'll call the pattern matching functions multiple times...
//
{

	//
	//	Copy the include/exclude filters if specified...
	//
	//
	if( IsEmptyString(szIncludeFilter) 
	&&	IsEmptyString(szExcludeFilter) )
	{
		return TRUE;//both the include && exclude filters aren't specified
					//no need to initialize the pattern matching function....
					//if filters are never used, then
					//one less dll is loaded.
	}

#ifdef _TESTING_WILDCMP_ONLY_
	s_hShlwapi_dll = NULL;
	s_bShlwapi_dllExists = FALSE;
	return InitializePatterns(szIncludeFilter, szExcludeFilter);
#endif


	if( s_hShlwapi_dll != NULL )
	{
		ASSERT( s_fpPatternMatchSpec != NULL );
		InterlockedIncrement(&s_nRefCnt_hShlwapi);

		return InitializePatterns(szIncludeFilter, szExcludeFilter);
	}
	else{
		if( s_bShlwapi_dllExists == TRUE )//either the dll exists, or we haven't tried loading it yet...
		{
			//The pattern match function hasn't been initialized yet....
			//
		
			s_hShlwapi_dll = ::LoadLibrary(_T("Shlwapi.dll"));
			if( s_hShlwapi_dll == NULL )
			{
				s_bShlwapi_dllExists = FALSE;//don't try loading this dll again.
				s_fpPatternMatchSpec = wildcmp;//even though it's set buy default, and this code will only get here once, set it just for fun.

				return InitializePatterns(szIncludeFilter, szExcludeFilter);
				
			}
			else
			{
				//Shlwapi.dll was found....check it for PathMatchSpec()
#ifdef UNICODE 
				s_fpPatternMatchSpec = (FUNC_PatternMatchSpec)::GetProcAddress(s_hShlwapi_dll, "PathMatchSpecW");
#else
				s_fpPatternMatchSpec = (FUNC_PatternMatchSpec)::GetProcAddress(s_hShlwapi_dll, "PathMatchSpecA");
#endif
	
				if( s_fpPatternMatchSpec != NULL )
				{
					//UsesRealPathMatchSpec() will now return true.
					//we're on NT w/ IE 4.0 or greater...(or Win2k/XP)
					InterlockedIncrement(&s_nRefCnt_hShlwapi);
					return InitializePatterns(szIncludeFilter, szExcludeFilter);
				}
				else
				{
					//we found shlwapi.dll, but it didn't have PathMatchSpec()
					::FreeLibrary( s_hShlwapi_dll );
					s_hShlwapi_dll = NULL;
					s_bShlwapi_dllExists = FALSE;

					//instead of using PathMatchSpec()
					//we'll use wildcmp()
					s_fpPatternMatchSpec = wildcmp;
					//UsesRealPathMatchSpec() will now return false w/out asserting.

					return InitializePatterns(szIncludeFilter, szExcludeFilter);
				}
			}
			
		}
	}
	return (s_fpPatternMatchSpec != NULL);
}

BOOL CDelayedDirectoryChangeHandler::InitializePatterns(LPCTSTR szIncludeFilter, LPCTSTR szExcludeFilter)
{
	ASSERT( !IsEmptyString(szIncludeFilter)   //one of these must have something in it, 
		||  !IsEmptyString(szExcludeFilter) );//or else this function shouldn't be called.

	if( s_hShlwapi_dll != NULL )
	{
		//we're using Shlwapi.dll's PathMatchSpec function....
		//we're running on NT4.0 w/ IE 4.0 installed, or win2k/winXP(or greater)
		//
		//	Copy the include/exclude filters if specified...
		//
		//
		// we're using the real PathMatchSpec() function which
		//	supports multiple pattern specs...(separated by a semi-colon)
		//	so there's only one filter spec as far as my code is concerned.
		//
		if( !IsEmptyString(szIncludeFilter) )
		{
			m_szIncludeFilter = _tcsdup(szIncludeFilter);
			ASSERT( m_szIncludeFilter );
			m_nNumIncludeFilterSpecs = 1;
		}
		if( !IsEmptyString(szExcludeFilter) )
		{
			m_szExcludeFilter = _tcsdup(szExcludeFilter);	
			ASSERT( m_szExcludeFilter );
			m_nNumExcludeFilterSpecs = 1;
		}	
	}
	else
	{
		//shlwapi.dll isn't on this machine.... can happen on NT4.0 w/ out IE 4.0 installed.
		ASSERT( s_bShlwapi_dllExists == FALSE );

		//
		//	we're using the function wildcmp() instead of PathMatchSpec..
		//
		//	this means that multiple pattern specs aren't supported...
		// in order to support them, we'll tokenize the string into multiple
		// pattern specs and test the string multiple times(once per pattern spec) 
		// in order to support multiple patterns.
		//
		//
		//	m_szIncludeFilter & m_szExclude filter will be used like TCHAR**'s instead of TCHAR*'s
		//

		m_nNumIncludeFilterSpecs = 0;
		if( !IsEmptyString(szIncludeFilter) )
		{
			TCHAR * szTmpFilter = _tcsdup(szIncludeFilter);
			TCHAR * pTok = _tcstok( szTmpFilter, _T(";"));
			while( pTok )
			{
				m_nNumIncludeFilterSpecs++;
				pTok = _tcstok(NULL, _T(";"));
			}
			if( m_nNumIncludeFilterSpecs == 1 )
				m_szIncludeFilter = _tcsdup(szIncludeFilter);
			else
			{   //allocate room for pointers .. one for each token...
				m_szIncludeFilter = (TCHAR*)malloc( m_nNumIncludeFilterSpecs * sizeof(TCHAR*));

				free(szTmpFilter);
				szTmpFilter = _tcsdup(szIncludeFilter);
				pTok = _tcstok(szTmpFilter, _T(";"));
				TCHAR ** ppTmp = (TCHAR**)m_szIncludeFilter;
				while(pTok)
				{
					*ppTmp = _tcsdup(pTok);
					ppTmp++;
					pTok = _tcstok(NULL, _T(";"));
				}
			}

			free(szTmpFilter);
		}

		//
		//	Do the same for the Exclude filter...
		//
		m_nNumExcludeFilterSpecs = 0;
		if( !IsEmptyString(szExcludeFilter) )
		{
			TCHAR * szTmpFilter = _tcsdup(szExcludeFilter);
			TCHAR * pTok = _tcstok( szTmpFilter, _T(";"));
			while( pTok )
			{
				m_nNumExcludeFilterSpecs++;
				pTok = _tcstok(NULL, _T(";"));
			}
			if( m_nNumExcludeFilterSpecs == 1 )
				m_szExcludeFilter = _tcsdup(szExcludeFilter);
			else
			{   //allocate room for pointers .. one for each token...
				m_szExcludeFilter = (TCHAR*)malloc( m_nNumExcludeFilterSpecs * sizeof(TCHAR*));

				free(szTmpFilter);
				szTmpFilter = _tcsdup(szExcludeFilter);

				pTok = _tcstok(szTmpFilter, _T(";"));
				TCHAR ** ppTmp = (TCHAR**)m_szExcludeFilter;
				while(pTok)
				{
					*ppTmp = _tcsdup(pTok);
					ppTmp++;
					pTok = _tcstok(NULL, _T(";"));
				}
			}
			free(szTmpFilter);
		}

	}

	return (m_szExcludeFilter!= NULL || (m_szIncludeFilter != NULL));
}
void CDelayedDirectoryChangeHandler::UninitializePathMatchFunc()
{
	if( s_bShlwapi_dllExists == TRUE 
	&&  s_hShlwapi_dll != NULL )
	{
		if( InterlockedDecrement(&s_nRefCnt_hShlwapi) <= 0)
		{
			s_nRefCnt_hShlwapi = 0;
			FreeLibrary( s_hShlwapi_dll );
			s_hShlwapi_dll = NULL;
			s_fpPatternMatchSpec = wildcmp;
		}
	}
}

bool CDelayedDirectoryChangeHandler::UsesRealPathMatchSpec() const
//are we using PathMatchSpec() or wildcmp()?
{
	if( s_hShlwapi_dll != NULL && s_fpPatternMatchSpec != NULL )
		return true;
	if( s_hShlwapi_dll == NULL && s_fpPatternMatchSpec != NULL )
		return false;

	ASSERT( FALSE );//this function was called before InitializePathMatchFunc()
	//oops!
	return false;
}
static inline bool HasTrailingBackslash(const CString & str )
{
	if( str.GetLength() > 0 
	&&	str[ str.GetLength() - 1 ] == _T('\\') )
		return true;
	return false;
}
void CDelayedDirectoryChangeHandler::SetPartialPathOffset(const CString & strWatchedDirName)
{
	if( m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_PARTIAL_PATH )
	{
		//set the offset to 
		if( HasTrailingBackslash( strWatchedDirName ) )
			m_dwPartialPathOffset = strWatchedDirName.GetLength();
		else
			m_dwPartialPathOffset = strWatchedDirName.GetLength() + 1;
	}
	else
		m_dwPartialPathOffset = 0;
}

CDirChangeNotification * CDelayedDirectoryChangeHandler::GetNotificationObject()
//
//	Maybe in future I'll keep a pool of these 
//	objects around to increase performance...
//	using objects from a cache will be faster 
//	than allocated and destroying a new one each time.
//	
//  
{
	ASSERT( m_pRealHandler );
	return new CDirChangeNotification(this, m_dwPartialPathOffset);//helps support FILTERS_CHECK_PARTIAL_PATH
}

void CDelayedDirectoryChangeHandler::DisposeOfNotification(CDirChangeNotification * pNotification)
{
	delete pNotification;
}

//These functions are called when the directory to watch has had a change made to it
void CDelayedDirectoryChangeHandler::On_FileAdded(const CString & strFileName)
{
	CDirChangeNotification * p = GetNotificationObject();
	ASSERT( p );
	if( p ) p->PostOn_FileAdded( strFileName );
}

void CDelayedDirectoryChangeHandler::On_FileRemoved(const CString & strFileName)
{
	CDirChangeNotification * p = GetNotificationObject();
	ASSERT( p );
	if( p ) p->PostOn_FileRemoved( strFileName );
}

void CDelayedDirectoryChangeHandler::On_FileModified(const CString & strFileName)
{
	CDirChangeNotification * p = GetNotificationObject();
	ASSERT( p );
	if( p ) p->PostOn_FileModified( strFileName );
}

void CDelayedDirectoryChangeHandler::On_FileNameChanged(const CString & strOldFileName, const CString & strNewFileName)
{
	CDirChangeNotification * p = GetNotificationObject();	
	ASSERT( p );
	if( p ) p->PostOn_FileNameChanged( strOldFileName, strNewFileName );
}

void CDelayedDirectoryChangeHandler::On_ReadDirectoryChangesError(DWORD dwError, const CString & strDirectoryName)
{
	CDirChangeNotification * p = GetNotificationObject();
	ASSERT( p );
	if( p ) p->PostOn_ReadDirectoryChangesError( dwError, strDirectoryName );
}

void CDelayedDirectoryChangeHandler::On_WatchStarted(DWORD dwError, const CString & strDirectoryName)
{
	if( !(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_NO_WATCHSTART_NOTIFICATION))
	{
		CDirChangeNotification * p = GetNotificationObject();

		if( p ) p->PostOn_WatchStarted(dwError, strDirectoryName);
	}
}

void CDelayedDirectoryChangeHandler::On_WatchStopped(const CString & strDirectoryName)
{
	if( !(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_NO_WATCHSTOP_NOTIFICATION))
	{
		CDirChangeNotification * p = GetNotificationObject();

		if( p ){
			if( m_hWatchStoppedDispatchedEvent )
				::ResetEvent(m_hWatchStoppedDispatchedEvent);

			p->PostOn_WatchStopped( strDirectoryName );

			//	Wait that this function has been dispatched to the other thread
			//	before continueing.  This object may be getting deleted
			//	soon after this function returns, and before the function can be
			//	dispatched to the other thread....
			WaitForOnWatchStoppedDispatched();
		}
	}
}


void CDelayedDirectoryChangeHandler::PostNotification(CDirChangeNotification * pNotification)
{
	if( m_pDelayNotifier )
		m_pDelayNotifier->PostNotification( pNotification );
}

inline bool IsNonFilterableEvent( CDirChangeNotification::eFunctionToDispatch eEvent)
// Helper function
//	For filtering events..... these functions can not be filtered out.
//
{
	if(	eEvent == CDirChangeNotification::eOn_WatchStarted 
	||	eEvent == CDirChangeNotification::eOn_WatchStopped
	||	eEvent == CDirChangeNotification::eOn_ReadDirectoryChangesError )
	{
		return true;
	}
	else
		return false;
}
DWORD GetPathOffsetBasedOnFilterFlags(CDirChangeNotification * pNot, DWORD dwFilterFlags)
{
	
	ASSERT( pNot && dwFilterFlags != 0 );
	//helps support the filter options FILTERS_CHECK_FULL_PATH, FILTERS_CHECK_PARTIAL_PATH, and FILTERS_CHECK_FILE_NAME_ONLY

	DWORD dwFileNameOffset = 0;//offset needed to support FILTERS_CHECK_FULL_PATH
	if( dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_FILE_NAME_ONLY )
	{
		//set the offset to support FILTERS_CHECK_FILE_NAME_ONLY
		TCHAR * pSlash  = _tcsrchr(pNot->m_szFileName1, _T('\\'));
		if( pSlash )
			dwFileNameOffset = (++pSlash - pNot->m_szFileName1);

		//
		//	Because file name change notifications take place in the same directory,
		//	the same dwFileNameOffset can be used for the szNewFileName(pNot->m_szFileName2)
		//	when checking the filter against the new file name.
		//
	}
	else
	if( dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_PARTIAL_PATH)
	{
		//
		//	partial path offset is the offset 
		//	from the beginning of the file name, 
		//	to the end of the watched directory path...
		//	ie: If you're watching "C:\Temp"
		//		and the file C:\Temp\SubFolder\FileName.txt" is changed,
		//		the partial path offset will give you "SubFolder\FileName.txt"
		//		when this is checked against the include/exclude filter.
		//
		dwFileNameOffset = pNot->m_dwPartialPathOffset;
	}
	//else
	//if( m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_FULL_PATH )
	//	dwFileNameOffset = 0;
	
	return dwFileNameOffset;
}

bool CDelayedDirectoryChangeHandler::NotifyClientOfFileChange(CDirChangeNotification * pNot)
//
//
//	Perform the tests to see if the client wants to be notified of this
//	file change notification.
//
//	Tests performed:
//
//	Event test:		Not all events can be filtered out.
//					On_ReadDirectoryChangesError
//					cannot be filtered out.
//	Filter flags test:  User can specify flags so that no tests are performed....all notifications are sent to the user.
//
//	Filter test:	Test the notification file name against include and exclude filters.
//
//					Only files changes matching the INCLUDE filter will be passed to the client.
//					By not specifying an include filter, all file changes are passed to the client.
//	
//				    Any files matching the EXCLUDE filter will not be passed to the client.
//
//
//					Note: For the file name change event:
//							If the old file name does not pass the tests for the include and exclude filter
//							but the NEW file name does pass the test for the filters, then the client IS notified.
//
//	Client test:	The CDirectoryChangeHandler derived class is given a chance to filter the event by calling
//					CDirectoryChangeHandler::On_FilterNotification()
//
//	RETURN VALUE:
//	If this function returns true, the notification function is called.
//	If it returns false, the notification is ignored.
//			The client is notified by calling CDirectoryChangeHandler's virtual functions On_FileAdded(),On_FileRemoved(), etc.
{
	ASSERT( pNot );
	ASSERT( m_pRealHandler );

	//
	//	Some events can't be ignored, or filtered out.
	//

	if( ((m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_ANY_FILTER_TESTS) == CDirectoryChangeWatcher::FILTERS_DONT_USE_ANY_FILTER_TESTS)
	||	IsNonFilterableEvent( pNot->m_eFunctionToDispatch ) )
	{
		// Either this is a non-filterable event, or we're not using any filters...
		// client is notified of all events..
		return true;
	}

	//
	//	See if user wants to test CDirectoryChangeHandler::On_FilterNotification()
	//	before tests are performed against the file name, and filter specifications
	//
	if( (m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_TEST_HANDLER_FIRST )//specified that CDirectoryChangeHandler::On_FilterNotification is to be called before any other filter tests
	&&	!(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_HANDLER_FILTER)//and did not specify that this CDirectoryChangeHandler::On_FilterNotification is not to be called..
	&&	!m_pRealHandler->On_FilterNotification(pNot->m_eFunctionToDispatch, pNot->m_szFileName1, pNot->m_szFileName2) )
	{
		//
		//	Client specified to test handler first, and it didn't pass the test... don't notify the client.
		//
		return false;
	}
	//else
	//
	//	this file change passed the user test, continue testing
	//	to see if it passes the filter tests.

	DWORD dwFileNameOffset = GetPathOffsetBasedOnFilterFlags(pNot, m_dwFilterFlags);

	//
	//	See if the changed file matches the include or exclude filter
	//	Only allow notifications for included files 
	//	that have not been exluded.
	//
	if(!(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_FILTERS) )
	{
		if( false == IncludeThisNotification(pNot->m_szFileName1 + dwFileNameOffset)
		||	true == ExcludeThisNotification(pNot->m_szFileName1 + dwFileNameOffset) )
		{
			if( pNot->m_eFunctionToDispatch != CDirChangeNotification::eOn_FileNameChanged )
				return false;
			else{
				//Special case for file name change:
				//
				// the old file name didn't pass the include/exclude filter
				// but if the new name passes the include/exclude filter, 
				// we will pass it on to the client...

				if( false == IncludeThisNotification(pNot->m_szFileName2 + dwFileNameOffset) 
				||	true == ExcludeThisNotification(pNot->m_szFileName2 + dwFileNameOffset) )
				{
					// the new file name didn't pass the include/exclude filter test either
					// so don't pass the notification on...
					return false;
				}

			}
		}

	}
	
	//
	//	Finally, let the client determine whether or not it wants this file notification
	//	if this test has not already been performed...
	//

	if( (m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_TEST_HANDLER_FIRST) 
	||	(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_HANDLER_FILTER) )
	{
		//	if we got this far, and this flag was specified, 
		//	it's already passed this test,
		//  or we're not checking it based on the filter flag FILTERS_DONT_USE_HANDLER_FILTER....
		return true;
	}
	else
	{
		if( m_pRealHandler->On_FilterNotification(pNot->m_eFunctionToDispatch,
											  pNot->m_szFileName1,
											  pNot->m_szFileName2) )
		{
			return true;
		}
		else
		{
			//else  client's derived CDirectoryChangeHandler class chose 
			//		not to be notified of this file change
			return false;
		}
	}
		
	
}

bool CDelayedDirectoryChangeHandler::IncludeThisNotification(LPCTSTR szFileName)
//
//	The Include filter specifies which file names we should allow to notifications
//	for... otherwise these notifications are not dispatched to the client's code.
//
//	Tests the file name to see if it matches a filter specification
//
//	RETURN VALUES:
//		
//		true : notifications for this file are to be included...
//			   notifiy the client by calling the appropriate CDirectoryChangeHandler::On_Filexxx() function.
//		false: this file is not included.... do not notifiy the client...
//
{
	ASSERT( szFileName );

	if( m_szIncludeFilter == NULL ) // no filter specified, all files pass....
		return true;
	if( m_nNumIncludeFilterSpecs == 1 )
	{
		return _PathMatchSpec(szFileName, m_szIncludeFilter)? true : false;
	}
	else
	{
		TCHAR ** ppTmp = (TCHAR**)m_szIncludeFilter;
		for(int i(0); i < m_nNumIncludeFilterSpecs; ++i )
		{
			if( _PathMatchSpec(szFileName, *ppTmp++) )
				return true;
		}
		return false;
	}

	return false;
}

bool CDelayedDirectoryChangeHandler::ExcludeThisNotification(LPCTSTR szFileName)
//
//	Tests the file name to see if it matches a filter specification
//	if this function returns true, it means that this notification
//	is NOT to be passed to the client.... changes to this kind of file
//	are not
//
//	RETURN VALUES:
//		
//		true :   notifications for this file are to be filtered out(EXCLUDED)...
//				 do not notifify the client code.
//		false:   notifications for this file are NOT to be filtered out
//
//
{

	ASSERT( szFileName );

	if( m_szExcludeFilter == NULL ) // no exclude filter... nothing is excluded...
		return false;
	if( m_nNumExcludeFilterSpecs == 1 )
	{
		if( _PathMatchSpec(szFileName, m_szExcludeFilter) )
			return true;//exclude this notification...
		return false;
	}
	else
	{
		TCHAR ** ppTmp = (TCHAR**)m_szExcludeFilter;
		for(int i(0); i < m_nNumExcludeFilterSpecs; ++i )
		{
			if( _PathMatchSpec(szFileName, *ppTmp++) )
				return true;//exclude this one...
		}
		return false;//didn't match any exclude filters...don't exclude it
	}
/**
	if( m_szExcludeFilter == NULL //no exclude filter specified, not excluding anything....
	||	!PathMatchSpec(szFileName, m_szExcludeFilter) )//or didn't match filter pattern.. this is not excluded...
	{
		return false;
	}
	return true;
***/
	
}

void CDelayedDirectoryChangeHandler::DispatchNotificationFunction(CDirChangeNotification * pNotification)
/*****************************************************
	This function is called when we want the notification to execute.

	
******************************************************/
{
	ASSERT( m_pRealHandler );
	ASSERT( pNotification );
	if( pNotification && m_pRealHandler )
	{
		//
		//	Allow the client to ignore the notification
		//
		//
		if( NotifyClientOfFileChange(pNotification))
		{
			switch( pNotification->m_eFunctionToDispatch )
			{
			case CDirChangeNotification::eOn_FileAdded:
				
				m_pRealHandler->On_FileAdded( pNotification->m_szFileName1 ); 
				break;
				
			case CDirChangeNotification::eOn_FileRemoved:
				
				m_pRealHandler->On_FileRemoved( pNotification->m_szFileName1 );
				break;
				
			case CDirChangeNotification::eOn_FileNameChanged:
				
				m_pRealHandler->On_FileNameChanged( pNotification->m_szFileName1, pNotification->m_szFileName2 );
				break;
				
			case CDirChangeNotification::eOn_FileModified:
				
				m_pRealHandler->On_FileModified( pNotification->m_szFileName1 );
				break;
				
			case CDirChangeNotification::eOn_ReadDirectoryChangesError:
				
				m_pRealHandler->On_ReadDirectoryChangesError( pNotification->m_dwError, pNotification->m_szFileName1 );
				break;
				
			case CDirChangeNotification::eOn_WatchStarted:
		
				m_pRealHandler->On_WatchStarted(pNotification->m_dwError, pNotification->m_szFileName1);
				break;
		
			case CDirChangeNotification::eOn_WatchStopped:
				
				try{
					//
					//	The exception handler is just in case of the condition described in DirectoryChanges.h
					//	in the comments for On_WatchStopped()
					//
					m_pRealHandler->On_WatchStopped(pNotification->m_szFileName1);

				}catch(...){
					MessageBeep( 0xffff );
					MessageBeep( 0xffff );
			#ifdef DEBUG
					MessageBox(NULL,_T("An RTFM Exception was raised in On_WatchStopped() -- see Comments for CDirectoryChangeHandler::On_WatchStopped() in DirectoryChanges.h."), _T("Programmer Note(DEBUG INFO):"), MB_ICONEXCLAMATION | MB_OK);
			#endif
				}
				//
				//	Signal that the On_WatchStopped() function has been dispatched.
				//
				if( m_hWatchStoppedDispatchedEvent )
					SetEvent(m_hWatchStoppedDispatchedEvent);
				break;
			case CDirChangeNotification::eFunctionNotDefined:
			default:
				break;
			}//end switch()
		}
	}	
	if( pNotification )						 //		
		DisposeOfNotification(pNotification);// deletes or releases the notification object from memory/use
											 //
}

BOOL CDelayedDirectoryChangeHandler::WaitForOnWatchStoppedDispatched( )
//
//	When shutting down, m_pRealHandler->On_WatchStopped() will be called.
//	Because it's possible that this object will be deleted before the notification
//	can be dispatched to the other thread, we have to wait until we know that it's been executed
//	before returning control.
//
//	This function signals that the function has been dispatched to the other
//	thread and it will be safe to delete this object once this has returned.
//
{
	ASSERT( m_hWatchStoppedDispatchedEvent );
	DWORD dwWait = WAIT_FAILED;
	if( m_hWatchStoppedDispatchedEvent )
	{

		if( m_bAppHasGUI == false )
		{
			//
			//	The function will be dispatched to another thread...
			//	just wait for the event to be signalled....
			do{
				dwWait	= WaitForSingleObject(m_hWatchStoppedDispatchedEvent, 5000);//wait five seconds
				if( dwWait != WAIT_OBJECT_0 )
				{
					TRACE(_T("WARNING: Possible Deadlock detected! ThreadID: %d File: %s Line: %d\n"), GetCurrentThreadId(), _T(__FILE__), __LINE__);
				}
			}while( dwWait != WAIT_OBJECT_0 );
		}
		else
		{
			//
			//	Note to self:  This thread doesn't have a message Q, and therefore can't attach to 
			//	receive messages and process them... MsgWaitForMultipleObjects won't wake up for messages 
			//	unless i attach myself the the other threads input Q....
			//	just use MsgWaitForMultipleObjects() in place of WaitForSingleObject in the places where it's used...
			//
			do{
				dwWait = MsgWaitForMultipleObjects(1, &m_hWatchStoppedDispatchedEvent, 
												   FALSE, 5000, 
												   QS_ALLEVENTS);//wake up for all events, sent messages, posted messages etc.
				switch(dwWait)
				{
				case WAIT_OBJECT_0:
					{
						//
						// The event has become signalled
						//
						
					}break;
				case WAIT_OBJECT_0 + 1: 
					{
						//
						//	There is a message in this thread's queue, so 
						//	MsgWaitForMultipleObjects returned.
						//	Process those messages, and wait again.

						MSG msg;
						while( PeekMessage(&msg, NULL, 0,0, PM_REMOVE ) ) 
						{
							if( msg.message != WM_QUIT)
							{
								TranslateMessage(&msg);
								DispatchMessage(&msg);
							}
							else
							{
								/****
								NOTE: putting WM_QUIT back in the Q caused problems. forget about it.
								****/
								break;
							}
						}
					}break;
				case WAIT_TIMEOUT:
					{
						TRACE(_T("WARNING: Possible Deadlock detected! ThreadID: %d File: %s Line: %d\n"), GetCurrentThreadId(), _T(__FILE__), __LINE__);
					}break;
				}
			}while( dwWait != WAIT_OBJECT_0 );
			ASSERT( dwWait == WAIT_OBJECT_0 );
		}

	}
	else
	{
		TRACE(_T("WARNING: Unable to wait for notification that the On_WatchStopped function has been dispatched to another thread.\n"));
		TRACE(_T("An Exception may occur shortly.\n"));
		TRACE(_T("File: %s Line: %d"), _T( __FILE__ ), __LINE__);
	
	}


	return (dwWait == WAIT_OBJECT_0 );
}

void CDelayedDirectoryChangeHandler::SetChangedDirectoryName(const CString & strChangedDirName)
{
	ASSERT( m_pRealHandler );
	CDirectoryChangeHandler::SetChangedDirectoryName(strChangedDirName);
	if( m_pRealHandler )
		m_pRealHandler->SetChangedDirectoryName( strChangedDirName );
}
const CString & CDelayedDirectoryChangeHandler::GetChangedDirectoryName() const
{
	if( m_pRealHandler )
		return m_pRealHandler->GetChangedDirectoryName();
	return CDirectoryChangeHandler::GetChangedDirectoryName();
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions