/////////////////////////////////////////////////////////////////////////////
// UtilityThread.cpp | Implementation of the TCUtilityThread class.
//

#include "pch.h"
#include "..\Inc\TCLib.h"
#include "UtilityThread.h"
#include "TCThread.h"


/////////////////////////////////////////////////////////////////////////////
// TCUtilityThread


/////////////////////////////////////////////////////////////////////////////
// Group=Types

/////////////////////////////////////////////////////////////////////////////
// Description: Nested class that manages a variable-length array of
// message arguments.
//
// Used by TCUtilityThread to manage a variable-length array of LPARAM
// arguments associated with an element of work in the utility thread's
// queue.
//
// This class represents a single element of work in the utility thread's
// queue. As such, it manages an identifier to indicate (to the
// TCUtilityThread-derived class) what type of work element is represented.
// Also, any data items associated with the element of work are managed as
// a variable-length array of LPARAM arguments. Since only the derived
// class understands the contents of the arguments, it is responsible for
// releasing them when the element of work is complete (or has failed).
// A callback function pointer is stored, which is called to perform any
// necessary cleanup of the arguments. Finally, if the derived class is a
// COM object, a reference to it will be held so that the object can not be
// released until the element of work completes (or fails).
//
// See Also: TCUtilityThread, TCUtilityThread::PostMessage,
// TCUtilityThread::PostMessageV, TCUtilityThread::PostMessageEx,
//
class TCUtilityThread::XWorkItem
{
// Group=Construction / Destruction
public:
  ///////////////////////////////////////////////////////////////////////////
  // Simply initializes the data members from the specified parameters.
  //
  // Parameters:
  //   punkOwner - The IUnknown of the TCUtilityThread-derived class. This
  // may be NULL if the derived-class is not a COM object. By copying this to
  // the m_punkOwner data member, AddRef is implicitly called since the data
  // member is a smart pointer.
  //   pfnRelease - A callback function pointer of type TC_UtilArgRelProc,
  // called to release any resources associated with the arguments.
  //   idMsg - An identifier, meaningful only in the context of the derive
  // class, used to identify the type of a queued element of work.
  //   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. These arguments are only
  // meaninful in the context of the derived class.
  //
  // See Also: TCUtilityThread::XWorkItem::destructor, TCUtilityThread,
  // TCUtilityThread::PostMessage, TCUtilityThread::PostMessageV,
  // TCUtilityThread::PostMessageEx, TCUtilityThread_ArgumentReleaseProc,
  // TC_UtilArgRelProc
  XWorkItem(IUnknown* punkOwner, TC_UtilArgRelProc pfnRelease, UINT idMsg,
    int cParams, LPARAM* rgParams) :
    m_punkOwner(punkOwner),
    m_pfnRelease(pfnRelease),
    m_idMsg(idMsg),
    m_vec(rgParams, rgParams + cParams)
  {
  }
  ///////////////////////////////////////////////////////////////////////////
  // If a callback function was specified for releasing the arguments, it is
  // called when the object is destroyed.
  //
  // If an IUnknown* of the derived-class was specified, an implicit Release
  // is performed.
  //
  // See Also: TCUtilityThread::XWorkItem::constructor, TCUtilityThread,
  // TCUtilityThread::PostMessage, TCUtilityThread::PostMessageV,
  // TCUtilityThread::PostMessageEx, TCUtilityThread_ArgumentReleaseProc,
  // TC_UtilArgRelProc
  XWorkItem::~XWorkItem()
  {
    if (m_pfnRelease)
// VS.Net 2003 port: see "Breaking Changes in the Standard C++ Library Since Visual C++ 6.0" in documentation
#if _MSC_VER >= 1310
	(*m_pfnRelease)(m_idMsg, m_vec.size(), &(*m_vec.begin()));
#else
	(*m_pfnRelease)(m_idMsg, m_vec.size(), m_vec.begin());
#endif
  }

// Group=Data Members
public:
  // IUnknown* of the owner object, if it's a COM object
  IUnknownPtr         m_punkOwner;
  // Callback function used to release the arguments.
  TC_UtilArgRelProc   m_pfnRelease;
  // Identifies the type of a queued element of work in the utility thread.
  UINT                m_idMsg;
  // A variable-length array of the arguments associated with a queued
  // element of work.
  std::vector<LPARAM> m_vec;
};


/////////////////////////////////////////////////////////////////////////////
// Static Initialization

/////////////////////////////////////////////////////////////////////////////
// Description: The number of TCUtilityThread (or derived class)
// instances referencing the shared utility thread.
//
// This static data member contains the number of TCUtilityThread (or derived
// class) instances referencing the shared utility thread.
//
// This member is packed on a DWORD boundary since it is specified as a
// parameter to the Win32 ::InterlockedIncrement and ::InterlockedDecrement
// API's.
//
// TODO: Currently, the interactions with this data member and m_pth does not
// appear to be completely thread safe. There should probably be a static
// TCAutoCriticalSection to control access to both of these data members.
// When that change is made, the data members will no longer need to be
// packed on DWORD boundaries, since the critical section would be used
// instead of the Win32 ::Interlocked... API's.
//
// See Also: TCUtilityThread::constructor, TCUtilityThread::destructor,
// TCUtilityThread::m_pth
long TCUtilityThread::m_nRefs = 0;

/////////////////////////////////////////////////////////////////////////////
// Description: A pointer to the shared utility thread.
//
// This static data member contains a pointer to the shared utility thread,
// or NULL if the thread has not yet been created.
//
// For each module (DLL or EXE) of a process, there can be, at most, one
// shared utility thread. This means, however, that a process may have
// multiple shared utility threads since the EXE can have one, and each DLL
// can have one.
//
// TODO: This could be alleviated by changing the implementation to use a
// process-wide shared utility thread. The static members could be put into a
// DLL and either a [local] COM object or (C-style) API exports could be
// provided to access (and ref-count) it. The [local] attribute of the COM
// object approach would make it possible to pass the pointer value *without*
// any marshaling. The [local] interface would *not* need to be
// [oleautomation], [dual], or even included in the type libary, although
// that would make it difficult to use the #import directive.
//
// This member is packed on a DWORD boundary since it may need to be
// specified as a parameter to the Win32 ::InterlockedExchange API.
//
// TODO: Currently, the interactions with this data member and m_nRefs does
// not appear to be completely thread safe. There should probably be a static
// TCAutoCriticalSection to control access to both of these data members.
// When that change is made, the data members will no longer need to be
// packed on DWORD boundaries, since the critical section would be used
// instead of the Win32 ::Interlocked... API's.
//
// See Also: TCThread, TCUtilityThread::constructor,
// TCUtilityThread::ThreadProc, TCUtilityThread::m_nRefs
TCThread* TCUtilityThread::m_pth = NULL;


/////////////////////////////////////////////////////////////////////////////
// Construction / Destruction

/////////////////////////////////////////////////////////////////////////////
// Increments the reference count of the shared utility thread. If this is
// the first instance, the utility thread is created.
//
// The copy constructor is declared private to disallow copying of objects of
// this type. It has only a declaration and no implementation.
//
// See Also: TCUtilityThread::m_nRefs, TCUtilityThread::m_pth
TCUtilityThread::TCUtilityThread() :
  m_bClosed(false)
{
  // Increment the static thread's reference count
  InterlockedIncrement(&m_nRefs);

  // If this is the first reference, create the thread
  if (1 == m_nRefs)
  {
    int nPriority = THREAD_PRIORITY_NORMAL;
    m_pth = TCThread::BeginThread(ThreadProc, NULL, nPriority, 4096);
  }
}

/////////////////////////////////////////////////////////////////////////////
// Calls Close to decrement the reference count of the utility thread.
//
// See Also: TCUtilityThread::constructor, TCUtilityThread::Close
TCUtilityThread::~TCUtilityThread()
{
  Close();
}


/////////////////////////////////////////////////////////////////////////////
// Group=Operations

/////////////////////////////////////////////////////////////////////////////
// Description: Decrements the reference count of the shared utility thread.
//
// Decrements the reference count of the shared utility thread. If the
// resulting reference count is zero, a WM_QUIT message is posted to the
// thread and the thread is waited upon to exit.
//
// The m_bClosed data member is used to disallow the class instance from
// decrementing the reference count more than once. This is important since
// the destructor calls Close.
//
// Note: This method is used internally, as noted above, by the destructor.
// It is made public to allow an advanced usage of the class. Most often,
// this method will *not* need to be called directly.
//
// TODO: The PostMessage methods should also check the m_bClosed flag prior
// to posting a message, but currently they don't. Not an issue until
// someone pokes an eye out calling PostMessage *after* calling close.
//
// See Also: TCUtilityThread::destructor, TCUtilityThread::m_bClosed,
// TCUtilityThread::m_nRefs, TCUtilityThread::m_pth
void TCUtilityThread::Close()
{
  if (!m_bClosed)
  {
    // Indicate that we have closed
    m_bClosed = true;

    // Decrement the reference count
    if (0 == InterlockedDecrement(&m_nRefs))
    {
      // Get the thread handle and ID
      assert(m_pth);
      HANDLE hth     = m_pth->m_hThread;
      DWORD idThread = m_pth->m_nThreadID;

      // Signal the thread to exit
      m_pth->PostThreadMessage(WM_QUIT, 0, 0);

      // Wait for the thread to exit
      if (GetCurrentThreadId() != idThread)
        WaitForSingleObject(hth, INFINITE);
    }
  }
}

/////////////////////////////////////////////////////////////////////////////
// Description: Posts an element of work to the shared utility thread's
// queue.
//
// Parameters:
//   idMsg - An identifier, meaningful only in the context of the derived
// class, used to identify the type of a queued element of work.
//   cParams - The number of LPARAM arguments pointed to by the either the
// /rgParams/ parameter, the /argptr/ parameter, or the variable argument
// list.
//   argptr - A variable argument list specifying the LPARAM arguments
// associated with the element of work. The number of parameters passed in
// must match the /cParams/ parameter.
//   rgParams - An array of LPARAM arguments specifying the LPARAM arguments
// associated with the element of work. This pointer must be valid for the
// number of LPARAM's specified by the /cParams/ parameter. These arguments
// are only meaninful in the context of the derived class.
//
// Remarks:
// These methods are used to post an element of work to the shared utility
// thread's message queue. An element of work consists of an identifier,
// /idMsg/, meaningful only in the context of the derived class, used to
// identify what type of work is represented by the message. Also, a variable
// number of LPARAM arguments can be associated with the element of work.
// Again, these arguments are only meaningful in the context of the derived
// class.
//
// Note: Rather than using function overloading and using the same name for
// all three methods, these *must* be named differently since the argument
// lists would be ambiguous.
//
// TODO: Create another set of these methods that take a timeout value as a
// parameter. This would allow an element of work to be delayed for a
// specified amount of time (in milliseconds). This could be implemented
// using the Win32 SetTimer/KillTimer API's with a static timer proc. The
// ThreadProc could capture the WM_TIMER message and map the timer ID to the
// XWorkItem instance. The functions should probably be prototyped as
// follows:
//
//      void PostTimedMessage(UINT uElapse, UINT idMsg, int cParams, ...);
//      void PostTimedMessageV(UINT uElapse, UINT idMsg, int cParams,
//        va_list argptr);
//      void PostTimedMessageEx(UINT uElapse, UINT idMsg, int cParams,
//        LPARAM* rgParams);
//
// TODO: Another useful feature would be to specify that an element of work
// is only to be processed if another element with the same message ID (and 
// owner instance) has not already been posted. This 'last of type' concept
// would be especially useful when combined with the timeout method. The
// implementation (in ThreadProc) could simply check a std::map for the
// owner/ID pair and, if found, ignore any XWorkItem instances other than
// the one mapped to the owner/ID pair. This would imply that such Post
// methods would add the most recent work element to the map. Possible
// prototypes for these might be as follows, where a /uElapse/ of zero would:
// indicate that the work element is not to be delayed, as in the original
// methods:
//
//      void PostLastOfMessage(UINT uElapse, UINT idMsg, int cParams, ...);
//      void PostLastOfMessageV(UINT uElapse, UINT idMsg, int cParams,
//        va_list argptr);
//      void PostLastOfMessageEx(UINT uElapse, UINT idMsg, int cParams,
//        LPARAM* rgParams);
//
// See Also: TCUtilityThread::XWorkItem, TCUtilityThread::ThreadProc
void TCUtilityThread::PostMessage(UINT idMsg, int cParams, ...)
{
  va_list argptr;
  va_start(argptr, cParams);
  PostMessageV(idMsg, cParams, argptr);
  va_end(argptr);
}

/////////////////////////////////////////////////////////////////////////////
// {partof:PostMessage}
void TCUtilityThread::PostMessageV(UINT idMsg, int cParams, va_list argptr)
{
  LPARAM* pParams = NULL;
  if (cParams)
    pParams = (LPARAM*)_alloca(cParams * sizeof(LPARAM));
  for (int i = 0; i < cParams; ++i)
    pParams[i] = va_arg(argptr, LPARAM);
  PostMessageEx(idMsg, cParams, pParams);
}

/////////////////////////////////////////////////////////////////////////////
// {partof:PostMessage}
void TCUtilityThread::PostMessageEx(UINT idMsg, int cParams, LPARAM* rgParams)
{
  assert(m_pth);
  IUnknown* punk = OnGetUnknown();
  TC_UtilArgRelProc pfnRelease = OnGetArgRelProc();
  XWorkItem* pArgs =
    new XWorkItem(punk, pfnRelease, idMsg, cParams, rgParams);
  if (!m_pth || !m_pth->PostThreadMessage(wm_message, WPARAM(this), LPARAM(pArgs)))
    DispatchWorkItem(pArgs);
}


/////////////////////////////////////////////////////////////////////////////
// Group=Overrides
#ifdef _DOCJET_ONLY
  ///////////////////////////////////////////////////////////////////////////
  // Description: Pure-virtual override to specify the derived-class's
  // IUnknown if it's a COM object.
  //
  // If the derived class is a COM object, its override of this method should
  // specify an IUnknown pointer on itself. This interface pointer is
  // AddRef'd and stored when a XWorkItem instance is created, and
  // Release'd when the instance is destroyed. This ensures that the derived
  // class instance is not released while elements of its work remain on the
  // queue.
  //
  // See Also: TCUtilityThread::ThreadProc, TCUtilityThread::XWorkItem,
  // TCUtilityThread::XWorkItem::m_punkOwner,
  // TCUtilityThread::PostMessage, TCUtilityThread::PostMessageV,
  // TCUtilityThread::PostMessageEx
  IUnknown* TCUtilityThread::OnGetUnknown();

  ///////////////////////////////////////////////////////////////////////////
  // Description: Pure-virtual override to specify a callback function used
  // to destroy the contents of a work element's arguments.
  //
  // The derived class defines the types of work that it will perform from
  // the shared utility thread. Because of this, it must provide the address
  // of a callback function that will release any resources associated with
  // the arguments of a queued element of work. When an element of work is
  // queued to the utility thread, this override is called to get the
  // callback function used to release the arguments.
  //
  // Note: Since the instance of the derived class that posted the element of
  // work may be destroyed before the work is performed, it was observed that
  // this function could *not* be virtual, but instead should be a
  // *static* class method. This was so that the callback function would
  // still be valid if the object were destroyed. If a virtual method were
  // used, accessing the the virtual table pointer would cause an exceptions.
  //
  // Return Value: The address of a callback function used to release the
  // arguments of a queued element of work. See
  // TCUtilityThread_ArgumentReleaseProc for the prototype of this function.
  //
  // See Also: TCUtilityThread_ArgumentReleaseProc, TC_UtilArgRelProc,
  // TCUtilityThread::ThreadProc, TCUtilityThread::XWorkItem,
  // TCUtilityThread::XWorkItem::m_pfnRelease,
  // TCUtilityThread::PostMessage, TCUtilityThread::PostMessageV,
  // TCUtilityThread::PostMessageEx
  TC_UtilArgRelProc TCUtilityThread::OnGetArgRelProc();

  ///////////////////////////////////////////////////////////////////////////
  // Description: Pure-virtual override to process a queued element of work.
  //
  // The derived class must override this pure-virtual method to process a
  // queued element of work. The parameters represent the same parameters
  // that were specified in one of the PostMessage calls.
  //
  // See the Class Overview for detailed information on how to implement
  // this.
  //
  // Parameters:
  //   idMsg - An identifier, meaningful only in the context of the derived
  // class, used to identify the type of a queued element of work.
  //   cParams - The number of LPARAM arguments pointed to by the
  // /rgParams/ parameter.
  //   rgParams - An array of LPARAM arguments specifying the LPARAM
  // arguments associated with the element of work. This pointer is valid
  // only for the number of LPARAM's specified by the /cParams/ parameter.
  // These arguments are only meaninful in the context of the derived class.
  //
  // See Also: TCUtilityThread::ThreadProc, TCUtilityThread::XWorkItem,
  // TCUtilityThread::XWorkItem::m_idMsg,
  // TCUtilityThread::XWorkItem::m_vec, TCUtilityThread::PostMessage,
  // TCUtilityThread::PostMessageV, TCUtilityThread::PostMessageEx
  void TCUtilityThread::OnMessage(UINT idMsg, int cParams, LPARAM* rgParams);
#endif // _DOCJET_ONLY


/////////////////////////////////////////////////////////////////////////////
// Group=Implementation

/////////////////////////////////////////////////////////////////////////////
// Description: The shared utility thread's main procedure.
//
// This static class method is the entry point for the shared utility thread.
// It's main role is to service the message queue, which is used to
// implement the queue of work elements to be processed by the shared utility
// thread.
//
// Aside from WM_QUIT, used to exit the message loop (and the thread), the
// only message expected to be posted to the message queue is wm_message,
// which is declared in a class enumeration as WM_APP. Upon receiving this
// message, the /wParam/ is cast as a pointer to the instance of
// TCUtilityThread that posted the element of work. Next, the /lParam/ is
// cast as a pointer to the XWorkItem instance that represents the queued
// element of work. The virtual OnMessage override is then called to allow
// the derived class to perform the work. Following the virtual method call,
// the XWorkItem instance is deleted which, in turn, will allow the
// derived class to release any resources represented by the arguments. Keep
// in mind that although the element of work is processed by the OnMessage
// override of the derived class, *the* *method* *is* *called* *in* *the* „
// *processing* *context* *of* *the* *shared* *utility* *thread.* This may
// pose little or no problem for many situations, but it deserves to be
// mentioned here.
//
// Note: The virtual method is called within a *__try* block in case the
// derived class instance has been destroyed. /If/ /the/ /derived/ /class/ „
// /is/ /a/ /COM/ /object,/ /the/ „
// /TCUtilityThread::XWorkItem::m_punkOwner/ /data/ /member/ /should/ „
// /have/ /been/ /set,/ /causing/ /an/ /AddRef./ /This/ /should/ „
// /circumvent/ /the/ /possibility/ /of/ /the/ /instance/ /being/ „
// /destroyed/ /while/ /it/ /still/ /has/ /elements/ /of/ /work/ /in/ /the/ „
// /queue./ However, this will also catch an ill-behaved override, so as to
// not unexpectedly crash the thread. The associated *__except* block simply
// sends a text message to the debug monitor but, mainly, serves the purpose
// of catching the exception in a consistent, well-defined manner. The
// message arguments instance is deleted *after* the entire exception block,
// to guarantee that it gets destructed properly.
// 
// Prior to entering the message loop, the thread is entered into the
// process's Multi-Threaded Apartment (MTA), as defined by the COM subsystem.
// The thread is removed from the Apartment after exiting the message loop,
// just prior to returning from the thread procedure.
//
// Parameters:
//   The void* paramter of the thread procedure prototype is not needed. It
// therefore is neither used nor declared in the parameter list.//
//
// Return Value: The return value is always zero and is only provided to
// satisfy the prototype of a thread procedure.
//
// See Also: TCUtilityThread::OnMessage, TCUtilityThread::PostMessage,
// TCUtilityThread::PostMessageV, TCUtilityThread::PostMessageEx,
// TCUtilityThread::XWorkItem
unsigned TCUtilityThread::ThreadProc(void*)
{
  TCERRLOG0("TCUtilityThread::ThreadProc(): Entering ThreadProc\n");

  // Enter this thread into the MTA
  HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  assert((SUCCEEDED(hr)));

  // Pump the message queue
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
  {
    switch (msg.message)
    {
      case wm_message:
      {
        TCUtilityThread* pThis = (TCUtilityThread*)msg.wParam;
        XWorkItem* pArgs = (XWorkItem*)msg.lParam;
        pThis->DispatchWorkItem(pArgs);
        break;
      }
    }   
  }

  // Clear the thread pointer since we're about to die
  m_pth = NULL;

  // Remove this thread from the MTA
  CoUninitialize();

  TCERRLOG0("TCUtilityThread::ThreadProc(): Exiting ThreadProc\n");
  return 0;
}

void TCUtilityThread::DispatchWorkItem(TCUtilityThread::XWorkItem* pArgs)
{
///  __try
	try
  {
// VS.Net 2003 port: see "Breaking Changes in the Standard C++ Library Since Visual C++ 6.0" in documentation
#if _MSC_VER >= 1310
    OnMessage(pArgs->m_idMsg, pArgs->m_vec.size(), &(*(pArgs->m_vec.begin())));
#else
    OnMessage(pArgs->m_idMsg, pArgs->m_vec.size(), pArgs->m_vec.begin());
#endif
  }
///  __except(1)
	catch(...)
  {
    TCERRLOG0("TCUtilityThread::DispatchThreadMessage: Exception Caught! Message loop continuing...\n");
  }
  delete pArgs;
}