#ifndef __UtilityThread_h__ #define __UtilityThread_h__ ///////////////////////////////////////////////////////////////////////////// // UtilityThread.h | Declaration of the TCUtilityThread class. // #include "TCThread.h" ///////////////////////////////////////////////////////////////////////////// // Type definition for a user-defined callback function that is called by the // implementation of TCUtilityThread. The callback function is expected to // release any resources associated with the specified thread arguments. // // A class derived from TCUtilityThread should override the virtual // TCUtilityThread::OnGetArgRelProc method and return a function pointer of // this type. Typically, the class will specify a static class method. // // See Also: TCUtilityThread, TCUtilityThread::OnGetArgRelProc, // TCUtilityThread::XWorkItem, TCUtilityThread_ArgumentReleaseProc typedef void (WINAPI *TC_UtilArgRelProc)(UINT, int, LPARAM*); #ifdef _DOCJET_ONLY /////////////////////////////////////////////////////////////////////////// // The TCUtilityThread_ArgumentReleaseProc function is an // application-defined callback function used with the TCUtilityThread // class. It receives notification that a queued element has finished // executing in the utility thread. The callback function should release // any resources associated with the specified arguments. The // TC_UtilArgRelProc type defines a pointer to this callback function. // TCUtilityThread_ArgumentReleaseProc is a placeholder for the // application-defined function name. // // Parameters: // idMsg - The message identifier used to identify different elements of // work that the TCUtilityThread-derived class has posted to the utility // thread. Note that this is *not* a window message and can be any UINT // value meaningful to the derived class. Usually, the derived class will // declare an enumeration in the class to define these identifiers. // cParams - The number of LPARAM arguments pointed to by the // /rgParams/ parameter. // rgParams - An array of LPARAM arguments specified when the element of // work was queued to the utility thread. // // See Also: TC_UtilArgRelProc, TCUtilityThread, // TCUtilityThread::OnGetArgRelProc, TCUtilityThread::PostMessage, // TCUtilityThread::PostMessageV, TCUtilityThread::PostMessageEx, // TCUtilityThread::XWorkItem void WINAPI TCUtilityThread_ArgumentReleaseProc(UINT idMsg, int cParams, LPARAM* rgParams); #endif // _DOCJET_ONLY ///////////////////////////////////////////////////////////////////////////// // TCUtilityThread provides a base class from which multiple classes can // inherit to perform elements of work within a single, module-wide thread. // // Possible uses for this class include: // + Performing delayed garbage collection. // + Performing less time-critical tasks outside of a main thread. // // To use this class, first include it as a public base in your // class declaration: // // class CMyClass : public TCUtilityThread // { // // It should rarely conflict with other base class method names, so it is // also suitable for use with multiple inheritance: // // class CMIClass : // public IUnknown, // public TCUtilityThread // { // // Next, declare the overrides for the pure virtual methods of the class: // // // Overrides // protected: // virtual IUnknown* OnGetUnknown(); // virtual TC_UtilArgRelProc OnGetArgRelProc(); // virtual void OnMessage(UINT idMsg, int cParams, LPARAM* rgParams); // // Following that, you should declare a *static* method used to release // message arguments: // // // Implementation // protected: // static void WINAPI ArgumentReleaseProc(UINT idMsg, int cParams, // LPARAM* rgParams); // // Then, declare an unnamed enumeration in your class, naming the type (or // types) of work that your class needs to perform from the utility thread: // // // Types // protected: // enum {e_NotifyEngineer, e_NotifyManager, e_NotifyExecutive}; // }; // // Next, implement OnGetUnknown. If your class is not a COM object, this can // simply return NULL: // // IUnknown* CMyClass::OnGetUnknown() // { // return NULL; // } // // But, if your class *does* implement a component object, you must return // an IUnknown pointer on your class. Do *not* AddRef the interface pointer: // // IUnknown* CMIClass::OnGetUnknown() // { // // ATL objects should use GetUnknown() instead of 'this' // return static_cast(this); // } // // The implementation of OnGetArgRelProc is just as easy: // // TC_UtilArgRelProc CMyClass::OnGetArgRelProc() // { // return ArgumentReleaseProc; // } // // Note: The OnGetArgRelProc can return NULL only if *all* the arguments // passed to the PostMessage, PostMessageV, or PostMessageEx methods do not // represent open resources. Open resources include things such as pointers, // handles, registry keys, etc. This is rarely the case, but if such // conditions do exists for your class, OnGetArgRelProc can return NULL and // the ArgumentReleaseProc static callback would not need to be declared or // implemented. // // The last of the overrides is where you actually perform the work for a // queued element. Usually, however, if multiple types of work are defined, // the method will just switch, unpack the arguments, and call a function to // peform one specific type of work: // // void CMyClass::OnMessage(UINT idMsg, int cParams, LPARAM* rgParams) // { // switch (idMsg) // { // case e_NotifyEngineer: // { // assert(2 == cParams); // CBonus* psi = reinterpret_cast(rgParams[0]); // LPCTSTR psz = reinterpret_cast(rgParams[1]); // NotifyEngineer(psi, psz); // return; // } // case e_NotifyManager: // { // assert(1 == cParams); // CWeatherRpt* pwr = reinterpret_cast(rgParams[0]); // NotifyManager(pwr); // return; // } // case e_NotifyExecutive: // { // assert(1 == cParams); // CBottomLine* pbl = reinterpret_cast(rgParams[0]); // NotifyExecutive(pbl); // return; // } // } // } // // Note: If you *really* dislike switch statements, you could specify a // class function pointer as the /idMsg/ parameter to the PostMessage // methods. Then, the switch statement (and the enum) could be eliminated. // Notice, though, that this approach would push the parameter unpacking down // to the individual member functions, which may also be considered more // elegant. // // void CMyClass::OnMessage(UINT idMsg, int cParams, LPARAM* rgParams) // { // // Declare a typedef for a member function pointer // typedef void (CMyClass::*WORK_PROC)(int, LPARAM*); // … // // Cast the idMsg back to a WORK_PROC // WORK_PROC pfn = (WORK_PROC)idMsg; // … // // Call the member function // (this->*pfn)(cParams, rgParams); // } // // Finally, the static method needs to be implemented to release the memory // used by the parameters: // // void WINAPI CMyClass::ArgumentReleaseProc(UINT idMsg, int cParams, // LPARAM* rgParams) // { // switch (idMsg) // { // case e_NotifyEngineer: // { // assert(2 == cParams); // CBonus* psi = reinterpret_cast(rgParams[0]); // LPCTSTR psz = reinterpret_cast(rgParams[1]); // delete psi; // delete [] psz; // return; // } // case e_NotifyManager: // { // assert(1 == cParams); // CWeatherRpt* pwr = reinterpret_cast(rgParams[0]); // delete pwr; // return; // } // case e_NotifyExecutive: // { // assert(1 == cParams); // CBottomLine* pbl = reinterpret_cast(rgParams[0]); // delete pbl; // return; // } // } // } // // You probably noticed that this method does most of the same things that // the virtual OnMessage method has to perform. It would seem likely that the // two operations (process and release) on the work element could be done in // the same method. A flag would indicate which operation to be performed on // the arguments once they're unpacked. This /could/ be done, but with one // minor caveat. Since the release method is static, the common function // would have to take great care to not reference any instance data or call // any non-static method. If this sounds like a reasonable tradeoff, the // static release method could be coded as follows: // // void WINAPI CMyClass::ArgumentReleaseProc(UINT idMsg, int cParams, // LPARAM* rgParams) // { // // Declare a typedef for a member function pointer // typedef void CMyClass::*WORK_PROC(int, LPARAM*); // … // // Cast the idMsg back to a WORK_PROC // WORK_PROC* pfn = (WORK_PROC*)idMsg; // … // // Create a NULL instance pointer (be careful with it!) // CMyClass* pThis = (CMyClass*)(NULL); // … // // Call the member function // (pThis->*pfn)(cParams, rgParams); // } // // For this to work correctly, the member function must *not* be virtual, and // it needs to check the *this* pointer to determine if it is to release the // arguments or to process them: // // void CMyClass::NotifyExecutive(int cParams, LPARAM* rgParams) // { // // Unpack the arguments // assert(1 == cParams); // CBottomLine* pbl = reinterpret_cast(rgParams[0]); // … // // Delete or process the arguments // if (this) // { // // Notify the executive of the bottom line... // } // else // { // delete pbl; // } // } // // Note: For a short discussion of the reason that the release method must be // static, refer to the documentation for the OnGetArgRelProc override. // // Finally, in the line of normal processing, when the derived object needs // to queue the element of work to the utility thread, a simple call to one // of the PostMessage methods is all that it takes: // // HRESULT CMyClass::ChangeBottomLine(double dRevenue, double dExpenses) // { // // Save the values, etc... // // ... // … // // Notify the executives of the change // CBottomLine* pbl = new CBottomLine(dRevenue, dExpenses); // PostMessage(e_NotifyExecutive, pbl); // … // // Indicate success // return S_OK; // } // // Of course, the PostMessage call would look slightly different if the // non-switch approach were being used: // // PostMessage(UINT(NotifyExecutive), pbl); // // ToDo: The majority of this class's functionality would be useful outside // of the singleton scenario. The argument handling and thread processing // should be moved into either a base class or another stand-alone class. // // See Also: TCUtilityThread_ArgumentReleaseProc, // TCUtilityThread::XWorkItem class TCUtilityThread { // Group=Types protected: class XWorkItem; // Construction / Destruction public: TCUtilityThread(); virtual ~TCUtilityThread(); // Disallow copy constructor private: TCUtilityThread(const TCUtilityThread&); // Attributes public: static bool IsCurrentThread(); // Operations public: void Close(); void PostMessage(UINT idMsg, int cParams, ...); void PostMessageV(UINT idMsg, int cParams, va_list argptr); void PostMessageEx(UINT idMsg, int cParams, LPARAM* rgParams); // Overrides protected: virtual IUnknown* OnGetUnknown() = 0; virtual TC_UtilArgRelProc OnGetArgRelProc() = 0; virtual void OnMessage(UINT idMsg, int cParams, LPARAM* rgParams) = 0; // Implementation protected: static unsigned WINAPI ThreadProc(void*); void DispatchWorkItem(XWorkItem* pArgs); // Group=Enumerations protected: enum {wm_message = WM_APP}; // Group=Data Members protected: // Description: A flag indicating that the Close method has been called. // // A flag indicating that the Close method has been called. This is used by // the Close method to ensure that the reference count of the utility // thread does not get decremented more than once by an instance. This is // important since the destructor calls Close. // // See Also: TCUtilityThread::destructor, TCUtilityThread::Close bool m_bClosed; #pragma pack(push, 4) static long m_nRefs; static TCThread* m_pth; #pragma pack(pop) }; ///////////////////////////////////////////////////////////////////////////// // Group=Attributes inline bool TCUtilityThread::IsCurrentThread() { return !m_pth || GetCurrentThreadId() == m_pth->m_nThreadID; } ///////////////////////////////////////////////////////////////////////////// #endif // !__UtilityThread_h__