//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1999 - 1999 // // File: events.h // // History: 09-24-1999 VivekJ created //-------------------------------------------------------------------------- /************************************************************************ * This file contains classes that provide support for the Observer pattern, * or publish-subscribe. * * Event sources send notifications to event observers. The parameterized type * is the event observer class, or (C++) interface. * * To use this mechanism, derive your event source class from CEventSource * parameterized by the observer class(es) or interface(es). Add a specific observer object * to the list of observers by calling the AddObserver function. The observer object class * should derive from CObserverBase. * * When the source wishes to call a particular notification method in the * observer class, use ScFireEvent template method. * specify observer's method as a 1st parameter. for instance: CMyObserver::MyEvent * [optionaly] specify parameters to event as 2nd and following parameters to ScFireEvent * * All event observer notification methods MUST return SC's. * * You can not have other method with the same name as event on observer - it won't compile. * * When sources/observers are deleted, the connection between them is broken automatically. * There is no support as yet to manually break a connection, although such a method can * be easily added to either the source or observer (make sure both sides of the connection * are broken. * * See the test code at the end of the file as an example. ************************************************************************/ // a #define to make it easy to implement only some observer methods. #define DEFAULT_OBSERVER_METHOD SC sc; return sc; #include "cpputil.h" /*+-------------------------------------------------------------------------* * class CObserverBase * * * PURPOSE: * *+-------------------------------------------------------------------------*/ class CObserverBase; /*+-------------------------------------------------------------------------* * class CEventSourceBase * * * PURPOSE: provides general Event Source interface * *+-------------------------------------------------------------------------*/ class CEventSourceBase { public: virtual ~CEventSourceBase() {} virtual void UnadviseSource(CObserverBase &observer) =0; }; /*+-------------------------------------------------------------------------* * class _CEventSource * * * PURPOSE: implements connection between event source and observer * * NOTE: This class IS NOT intended for external use - use CEventSource instead * *+-------------------------------------------------------------------------*/ template class _CEventSource : public CEventSourceBase { private: typedef CObserver * POBSERVER; typedef _CEventSource ThisClass; // no std::set, or std::map please // those cannot be shared by several DLL's // see KB article Q172/3/96 // since some items may be gone and some new added during the operation, // std::list is the best choice, since it does not reallocate on insert // but that is not enough - see CListIntegrityProtector class below struct ObserverData { POBSERVER pObject; // pointer to the object bool bDeleted; // true means entry remove pending, pObject not valid }; typedef std::list< ObserverData > CObserverList; CObserverList m_observerList; // count of active locks. // when value is 0, it is safe to remove deleted items from the list. int m_nStackDepth; // removes deleted items from the list void CleanupDeleted(); protected: typedef CObserverList::iterator iterator; /***************************************************************************\ * * CLASS: CListIntegrityProtector * * PURPOSE: locks the list in the owning class from item deletion. * thus assures the list is safe to iterate, the client just * needs to skip deleted entries. * Destructor will release the lock, an in case this is the last * lock - will perforn the cleanup. * * this technique is needed, since objects get deleted and created * during the events, so the observer list is changed, which * invalidated the iterators. Using the std::list and postponing * cleanup allowed to deal with 'live' list safelly. * * USAGE: Create the object of this class in scope where you need to * make sure the items does not get deleted from the list. * \***************************************************************************/ friend class CListIntegrityProtector; class CListIntegrityProtector { DECLARE_NOT_COPIABLE(CListIntegrityProtector) DECLARE_NOT_ASSIGNABLE(CListIntegrityProtector) ThisClass *m_pOwner; public: // constructor (increments iterator count) CListIntegrityProtector(ThisClass *pOwner) : m_pOwner(pOwner) { ASSERT(pOwner); if (pOwner) ++(pOwner->m_nStackDepth); } // destructor (decrements iterator count - cleans up if not iterators left) ~CListIntegrityProtector() { ASSERT(m_pOwner); if ( m_pOwner && ( 0 == --(m_pOwner->m_nStackDepth) ) ) m_pOwner->CleanupDeleted(); } }; protected: CObserverList & GetObserverList() { return m_observerList;} public: _CEventSource() : m_nStackDepth(0) {}; virtual ~_CEventSource(); // disconnects all listeners void _AddObserver(CObserver &observer); virtual void UnadviseSource(CObserverBase &observer); // to disconnect a particular observer. (only disconnects this side) }; /***************************************************************************\ * * CLASSES: _CEventSource1 - _CEventSource5 * * PURPOSE: these classes do not add anything to _CEventSource * defining them is just required for using them as base classes * of CEventSource_ [ same class cannot appear twice in base class list] * * NOTE: These classes ARE NOT intended for external use * \***************************************************************************/ template class _CEventSource1 : public _CEventSource {}; template class _CEventSource2 : public _CEventSource {}; template class _CEventSource3 : public _CEventSource {}; template class _CEventSource4 : public _CEventSource {}; template class _CEventSource5 : public _CEventSource {}; /*+-------------------------------------------------------------------------* * CVoid * * This is a do-nothing class that is used for the _CEventSourceX * specializations below. _CEventSourceX used specializations of "void" * and the default types for the Es2-Es5 template parameters of CEventSource * used to be "void". Newer (i.e. Win64) compilers, however, will issue * C2182 ("illegal use of type 'void'") and won't allow void to be the * default type. *--------------------------------------------------------------------------*/ class CVoid {}; /***************************************************************************\ * * SPECIALIZATIONS: _CEventSource1 - _CEventSource5 for void parameters * * PURPOSE: This allows to have single template for different count of observers. * The specialization defines empty and harmless base clases * for default template parameters * * NOTE: These classes ARE NOT intended for external use * \***************************************************************************/ template<> class _CEventSource2 {}; // specializes _CEventSource2 as empty class template<> class _CEventSource3 {}; // specializes _CEventSource3 as empty class template<> class _CEventSource4 {}; // specializes _CEventSource4 as empty class template<> class _CEventSource5 {}; // specializes _CEventSource5 as empty class /***************************************************************************\ * * CLASS: CEventSource * * PURPOSE: This class implements Event emitting capability to be used by * event-emitter classes derived from it. * Implements ScFireEvent methods and AddObserver method * * USAGE: use spacialization of this class as base class for your event-emitter class * See the list of possible usage paterns below: * class CMyClassWithEvents : public CEventSource ... * class CMyClassWithEvents : public CEventSource ... * ... * class CMyClassWithEvents : public CEventSource ... * * NOTE: Do not derive from this class more than once - add template parameters instead * \***************************************************************************/ template class CEventSource : public _CEventSource1, public _CEventSource2, // NOTE: this will be empty class if Es2 == CVoid public _CEventSource3, // NOTE: this will be empty class if Es3 == CVoid public _CEventSource4, // NOTE: this will be empty class if Es4 == CVoid public _CEventSource5 // NOTE: this will be empty class if Es5 == CVoid { public: /***************************************************************************\ * * METHOD: CEventSource::ScFireEvent * * PURPOSE: implements ScFireEvent for parameter-less events * * PARAMETERS: * SC (Observer::*_EventName)() - pointer to Observer's method * * RETURNS: * SC - result code * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template SC ScFireEvent(SC (observerclass::*_EventName)()) { DECLARE_SC(sc, TEXT("CEventSource::ScFireEvent - no parameters")); typedef _CEventSource BC; BC::CListIntegrityProtector(this); // protect the list while iterating for(BC::iterator iter = BC::GetObserverList().begin(); iter != BC::GetObserverList().end(); ++iter) { // skip deleted objects if (iter->bDeleted) continue; // sanity check sc = ScCheckPointers( iter->pObject, E_UNEXPECTED); if (sc) return sc; // invoke method "_EventName" on object pointed by *iter sc = ((iter->pObject)->*_EventName)(); if(sc) return sc; } return sc; } /***************************************************************************\ * * METHOD: CEventSource::ScFireEvent * * PURPOSE: implements ScFireEvent for events with one parameter * * PARAMETERS: * SC (Observer::*_EventName)() - pointer to Observer's method * _P1 p1 - parameter to be passed * * RETURNS: * SC - result code * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template SC ScFireEvent(SC (observerclass::*_EventName)(_P1 p1), _P1 p1) { DECLARE_SC(sc, TEXT("CEventSource::ScFireEvent - one parameter")); typedef _CEventSource BC; BC::CListIntegrityProtector(this); // protect the list while iterating for(BC::iterator iter = BC::GetObserverList().begin(); iter != BC::GetObserverList().end(); ++iter) { // skip deleted objects if (iter->bDeleted) continue; // sanity check sc = ScCheckPointers( iter->pObject, E_UNEXPECTED); if (sc) return sc; // invoke method "_EventName" on object pointed by *iter sc = ((iter->pObject)->*_EventName)(p1); if(sc) return sc; } return sc; } /***************************************************************************\ * * METHOD: CEventSource::ScFireEvent * * PURPOSE: implements ScFireEvent for events with two parameters * * PARAMETERS: * SC (Observer::*_EventName)() - pointer to Observer's method * _P1 p1 - parameter to be passed * _P2 p2 - parameter to be passed * * RETURNS: * SC - result code * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template SC ScFireEvent(SC (observerclass::*_EventName)(_P1 p1, _P2 p2), _P1 p1, _P2 p2) { DECLARE_SC(sc, TEXT("CEventSource::ScFireEvent - two parameters")); typedef _CEventSource BC; BC::CListIntegrityProtector(this); // protect the list while iterating for(BC::iterator iter = BC::GetObserverList().begin(); iter != BC::GetObserverList().end(); ++iter) { // skip deleted objects if (iter->bDeleted) continue; // sanity check sc = ScCheckPointers( iter->pObject, E_UNEXPECTED); if (sc) return sc; // invoke method "_EventName" on object pointed by *iter sc = ((iter->pObject)->*_EventName)(p1, p2); if(sc) return sc; } return sc; } /***************************************************************************\ * * METHOD: CEventSource::ScFireEvent * * PURPOSE: implements ScFireEvent for events with three parameters * * PARAMETERS: * SC (Observer::*_EventName)() - pointer to Observer's method * _P1 p1 - parameter to be passed * _P2 p2 - parameter to be passed * _P3 p3 - parameter to be passed * * RETURNS: * SC - result code * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template SC ScFireEvent(SC (observerclass::*_EventName)(_P1 p1, _P2 p2, _P3 p3), _P1 p1, _P2 p2, _P3 p3) { DECLARE_SC(sc, TEXT("CEventSource::ScFireEvent - three parameters")); typedef _CEventSource BC; BC::CListIntegrityProtector(this); // protect the list while iterating for(BC::iterator iter = BC::GetObserverList().begin(); iter != BC::GetObserverList().end(); ++iter) { // skip deleted objects if (iter->bDeleted) continue; // sanity check sc = ScCheckPointers( iter->pObject, E_UNEXPECTED); if (sc) return sc; // invoke method "_EventName" on object pointed by *iter sc = ((iter->pObject)->*_EventName)(p1, p2, p3); if(sc) return sc; } return sc; } /***************************************************************************\ * * METHOD: CEventSource::ScFireEvent * * PURPOSE: implements ScFireEvent for events with four parameters * * PARAMETERS: * SC (Observer::*_EventName)() - pointer to Observer's method * _P1 p1 - parameter to be passed * _P2 p2 - parameter to be passed * _P3 p3 - parameter to be passed * _P4 p4 - parameter to be passed * * RETURNS: * SC - result code * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template SC ScFireEvent(SC (observerclass::*_EventName)(_P1 p1, _P2 p2, _P3 p3, _P4 p4), _P1 p1, _P2 p2, _P3 p3, _P4 p4) { DECLARE_SC(sc, TEXT("CEventSource::ScFireEvent - three parameters")); typedef _CEventSource BC; BC::CListIntegrityProtector(this); // protect the list while iterating for(BC::iterator iter = BC::GetObserverList().begin(); iter != BC::GetObserverList().end(); ++iter) { // skip deleted objects if (iter->bDeleted) continue; // sanity check sc = ScCheckPointers( iter->pObject, E_UNEXPECTED); if (sc) return sc; // invoke method "_EventName" on object pointed by *iter sc = ((iter->pObject)->*_EventName)(p1, p2, p3, p4); if(sc) return sc; } return sc; } /***************************************************************************\ * * METHOD: CEventSource::ScFireEvent * * PURPOSE: implements ScFireEvent for events with four parameters * * PARAMETERS: * SC (Observer::*_EventName)() - pointer to Observer's method * _P1 p1 - parameter to be passed * _P2 p2 - parameter to be passed * _P3 p3 - parameter to be passed * _P4 p4 - parameter to be passed * _P5 p5 - parameter to be passed * * RETURNS: * SC - result code * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template SC ScFireEvent(SC (observerclass::*_EventName)(_P1 p1, _P2 p2, _P3 p3, _P4 p4, _P5 p5), _P1 p1, _P2 p2, _P3 p3, _P4 p4, _P5 p5) { DECLARE_SC(sc, TEXT("CEventSource::ScFireEvent - three parameters")); typedef _CEventSource BC; BC::CListIntegrityProtector(this); // protect the list while iterating for(BC::iterator iter = BC::GetObserverList().begin(); iter != BC::GetObserverList().end(); ++iter) { // skip deleted objects if (iter->bDeleted) continue; // sanity check sc = ScCheckPointers( iter->pObject, E_UNEXPECTED); if (sc) return sc; // invoke method "_EventName" on object pointed by *iter sc = ((iter->pObject)->*_EventName)(p1, p2, p3, p4, p5); if(sc) return sc; } return sc; } /***************************************************************************\ * * METHOD: CEventSource::AddObserver * * PURPOSE: adds observer to the list * * PARAMETERS: * Observer &observer - observer to add to the list * * RETURNS: * * NOTE: It must be both declared and defined here - will not compile else * \***************************************************************************/ template void AddObserver(observerclass &observer) { typedef _CEventSource BC; // NOTE: if you are getting the error here, probably you are passing a type // derived from actual Observer class to AddObserver(). Please cast it to appr. type BC::_AddObserver(observer); } }; /*+-------------------------------------------------------------------------* * class CObserverBase * * * PURPOSE: * *+-------------------------------------------------------------------------*/ class CObserverBase { typedef CEventSourceBase * PEVENTSOURCE; // no std::set, or std::map please // those cannot be shared by several DLL's // see KB article Q172/3/96 (Q172396) typedef std::list CSourceList; // list of all event sources that this object is connected to. typedef CSourceList::iterator iterator; CSourceList m_sourceList; CSourceList & GetSourceList() { return m_sourceList;} public: CObserverBase() {}; virtual ~CObserverBase(); void UnadviseObserver(CEventSourceBase &source); // to disconnect a particular source (this side only) void _AddSource(CEventSourceBase &source); void UnadviseAll(); // disconnects all connections - both sides }; //############################################################################ //############################################################################ // // Implementation of class CObserverBase // //############################################################################ //############################################################################ /*+-------------------------------------------------------------------------* * * CObserverBase::UnadviseObserver * * PURPOSE: * * PARAMETERS: * CEventSourceBase & source : * * RETURNS: * void * *+-------------------------------------------------------------------------*/ inline void CObserverBase::UnadviseObserver(CEventSourceBase &source) { DECLARE_SC(sc, TEXT("CObserverBase::UnadviseObserver")); iterator it = std::find( GetSourceList().begin(), GetSourceList().end(), &source ); // check if found if ( it == GetSourceList().end() ) { sc = E_UNEXPECTED; return; } GetSourceList().erase(it); } inline void CObserverBase::_AddSource(CEventSourceBase &source) { GetSourceList().push_back(&source); } /*+-------------------------------------------------------------------------* * * CObserverBase::~CObserverBase * * PURPOSE: Destructor * *+-------------------------------------------------------------------------*/ inline CObserverBase::~CObserverBase() { // disconnect all sources connected to this observer. iterator iter; for(iter = GetSourceList().begin(); iter != GetSourceList().end(); iter++) { (*iter)->UnadviseSource(*this); } } //############################################################################ //############################################################################ // // Implementation of class CEventSource // //############################################################################ //############################################################################ /*+-------------------------------------------------------------------------* * * _CEventSource::_AddObserver * * PURPOSE: * * PARAMETERS: * CObserver & observer : * *+-------------------------------------------------------------------------*/ template void _CEventSource::_AddObserver(CObserver &observer) { ObserverData observerData = { &observer, false /*bDeleted*/ }; GetObserverList().push_back( observerData ); observer._AddSource(*this); } /*+-------------------------------------------------------------------------* * * _CEventSource::~_CEventSource * * PURPOSE: Destructor * *+-------------------------------------------------------------------------*/ template _CEventSource::~_CEventSource() { //disconnect all observers connected to this source { CListIntegrityProtector(this); // protect the list while iterating iterator iter; for(iter = GetObserverList().begin(); iter != GetObserverList().end(); ++iter) { if (!iter->bDeleted && iter->pObject) iter->pObject->UnadviseObserver(*this); } } ASSERT( m_nStackDepth == 0 ); GetObserverList().clear(); } /*+-------------------------------------------------------------------------* * * _CEventSource::CleanupDeleted() * * PURPOSE: removes entries marked as deleted * *+-------------------------------------------------------------------------*/ template void _CEventSource::CleanupDeleted() { ASSERT ( m_nStackDepth == 0 ); for(iterator iter = GetObserverList().begin(); iter != GetObserverList().end();) { if (iter->bDeleted) iter = GetObserverList().erase( iter ); else ++iter; // valid - skip } } /*+-------------------------------------------------------------------------* * * CEventSource::UnadviseSource * * PURPOSE: * * PARAMETERS: * CObserver & observer : * *+-------------------------------------------------------------------------*/ template void _CEventSource::UnadviseSource(CObserverBase &observer) { // we cannot dynamic cast the observer pointer to a CObserver pointer, // becuase UnadviseSource is called from ~CObserverBase(), by which time // the CObserver sub-object has already been deleted. Instead, we search for // the CObserverBase pointer in the Observer List. iterator iter, iterNext; bool bFound = false; CListIntegrityProtector(this); // protect the list while iterating for(iter = GetObserverList().begin(); iter != GetObserverList().end(); iter++) { if(!iter->bDeleted && static_cast(iter->pObject) == &observer) { // do not alter the list directly, let the cleanup do the work // just mark the entry as invalid iter->bDeleted = true; iter->pObject = NULL; bFound = true; break; } } ASSERT(bFound); } //############################################################################ //############################################################################ // // stoopid test code // //############################################################################ //############################################################################ #ifdef TEST_EVENTS class CTestObserver : public CObserverBase { public: SC ScMyEvent(int a) { ASSERT(0 && "Reached here!"); return S_OK; } }; class CTestObserver2 : public CObserverBase { public: SC ScMyEvent(int a) { ASSERT(0 && "Reached here!"); return S_OK; } SC ScMyEvent2(int a,int d) { ASSERT(0 && "Reached here!"); return S_OK; } }; class CTestObserver3 : public CObserverBase { public: SC ScMyOtherEvent() { ASSERT(0 && "Reached here!"); return S_OK; } }; class CTestEventSource : public CEventSource { public: void FireEvent() { DECLARE_SC(sc, TEXT("FireEvent")); sc = ScFireEvent(CTestObserver::ScMyEvent, 42 /*arg1*/); sc = ScFireEvent(CTestObserver2::ScMyEvent2, 42 /*arg1*/, 24 /*arg1*/); sc = ScFireEvent(CTestObserver3::ScMyOtherEvent); } }; static void DoEventTest() { CTestEventSource source; CTestObserver observer_1; CTestObserver2 observer_2; CTestObserver3 observer_3; source.AddObserver(observer_2); source.AddObserver(observer_3); source.AddObserver(observer_1); source.FireEvent(); // should fire to observer1 only. { // new scope CTestObserver observer2; source.AddObserver(observer2); source.FireEvent(); // should fire to observer1 and observer2. // observer2 is deleted here. } source.FireEvent(); // should fire to observer1 only. } class CTestObject { public: CTestObject() { DoEventTest(); } }; #endif