Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

746 lines
25 KiB

//-----------------------------------------------------------------------------
//
//
// File: destmsgq.h
//
// Description:
// Header file for CDestMsgQueue class.
//
// Author: mikeswa
//
// Copyright (C) 1997 Microsoft Corporation
//
//-----------------------------------------------------------------------------
#ifndef _DESTMSGQ_H_
#define _DESTMSGQ_H_
#include "cmt.h"
#include <fifoq.h>
#include <rwnew.h>
#include "domain.h"
#include "aqroute.h"
#include <listmacr.h>
#include "aqutil.h"
#include "aqinst.h"
#include "aqstats.h"
#include "aqadmsvr.h"
class CLinkMsgQueue;
class CMsgRef;
class CAQSvrInst;
class CQuickList;
#define DESTMSGQ_SIG ' QMD'
#define DESTMSGRETRYQ_SIG 'QRMD'
#define EMPTY_DMQ_EXPIRE_TIME_MINUTES 1
class CDestMsgQueue;
//---[ CDestMsgRetryQueue ]----------------------------------------------------
//
//
// Hungarian: dmrq, pmdrq
//
// Provides a retry interface for requeuing messages to DMQ. If there are
// any outstanding messages for a queue, then someone must hold a reference
// to this intertace to requeue it.
//
// This class can only be created as as member of a CDestMsgQueue
//
//-----------------------------------------------------------------------------
class CDestMsgRetryQueue
{
protected:
DWORD m_dwSignature;
//Reference count for retry interface.
//Count is used to determine if it is safe to remove this DMQ from the
//DMT. This queue will only be removed when it has no messages and this
//count is zero. The count represents the total number of
//messages pending Ack on this queue. This is held while the message
//is sent over the wire, and we determine if the message needs
//to be retried.
DWORD m_cRetryReferenceCount;
CDestMsgQueue *m_pdmq;
friend class CDestMsgQueue;
CDestMsgRetryQueue();
~CDestMsgRetryQueue() {_ASSERT(!m_cRetryReferenceCount);};
public:
DWORD AddRef()
{return InterlockedIncrement((PLONG) &m_cRetryReferenceCount);};
DWORD Release()
{return InterlockedDecrement((PLONG) &m_cRetryReferenceCount);};
HRESULT HrRetryMsg(IN CMsgRef *pmsgref); //put message on retry queue
VOID CheckForStaleMsgsNextDSNGenerationPass();
};
//---[ CDestMsgQueue ]---------------------------------------------------------
//
//
// Hungarian: dmq, pmdq
//
// Provides a priority queue of MsgRef's for the CMT
//-----------------------------------------------------------------------------
class CDestMsgQueue :
public IQueueAdminAction,
public IQueueAdminQueue,
public CBaseObject
{
public:
CDestMsgQueue(CAQSvrInst *paqinst,
CAQMessageType *paqmtMessageType, IMessageRouter *pIMessageRouter);
~CDestMsgQueue();
HRESULT HrInitialize(IN CDomainMapping *pdmap);
HRESULT HrDeinitialize();
//Set the routing information for this domain
void SetRouteInfo(CLinkMsgQueue *plmq);
//Queue operations
inline HRESULT HrEnqueueMsg(IN CMsgRef *pmsgref, BOOL fOwnsTypeRef);
//Dequeue a message for delivery. All OUT params are ref-counted, and
//caller is responsable for releasing
HRESULT HrDequeueMsg(
IN DWORD dwLowestPriority, //Lowest priority message that will be dequeued
OUT CMsgRef **ppmsgref, //MsgRef dequeued
OUT CDestMsgRetryQueue **ppdmrq); //retry interface (optional)
inline void GetDomainMapping(OUT CDomainMapping **ppdmap);
//Remerge the retry queue with queues & generate DSNs if required
HRESULT HrGenerateDSNsIfNecessary(IN CQuickList *pqlQueues,
IN HRESULT hrConnectionStatus,
IN OUT DWORD *pdwContext);
//functions used to manipulate lists of queues
inline CAQMessageType *paqmtGetMessageType();
inline IMessageRouter *pIMessageRouterGetRouter();
inline BOOL fIsSameMessageType(CAQMessageType *paqmt);
static inline CDestMsgQueue *pdmqIsSameMessageType(
CAQMessageType *paqmt,
PLIST_ENTRY pli);
static inline CDestMsgQueue *pdmqGetDMQFromDomainListEntry(PLIST_ENTRY pli);
//Accessor functions for DomainEntry list
inline void InsertQueueInDomainList(PLIST_ENTRY pliHead);
inline void RemoveQueueFromDomainList();
inline PLIST_ENTRY pliGetNextDomainListEntry();
//Accessor functions for "empty-queue" list
void MarkQueueEmptyIfNecessary();
inline void InsertQueueInEmptyQueueList(PLIST_ENTRY pliHead);
inline void RemoveQueueFromEmptyQueueList();
inline PLIST_ENTRY pliGetNextEmptyQueueListEntry();
inline DWORD dwGetDMQState();
inline void MarkDMQInvalid();
void RemoveDMQFromLink(BOOL fNotifyLink);
//Addref and get link (returns NULL if not routed)
CLinkMsgQueue *plmqGetLink();
static inline CDestMsgQueue *pdmqGetDMQFromEmptyListEntry(PLIST_ENTRY pli);
//Method that external users can use to verify the signature for
//DMQ's passed as contexts or LIST_ENTRY's
inline void AssertSignature() {_ASSERT(DESTMSGQ_SIG == m_dwSignature);};
static HRESULT HrWalkDMQForDSN(IN CMsgRef *pmsgref, IN PVOID pvContext,
OUT BOOL *pfContinue, OUT BOOL *pfDelete);
static HRESULT HrWalkQueueForShutdown(IN CMsgRef *pmsgref,
IN PVOID pvContext, OUT BOOL *pfContinue,
OUT BOOL *pfDelete);
static HRESULT HrWalkRetryQueueForShutdown(IN CMsgRef *pmsgref,
IN PVOID pvContext, OUT BOOL *pfContinue,
OUT BOOL *pfDelete);
//Called by link to get & set link context
inline PVOID pvGetLinkContext() {return m_pvLinkContext;};
inline void SetLinkContext(IN PVOID pvLinkContext) {m_pvLinkContext = pvLinkContext;};
inline BOOL fIsRouted() {return (m_plmq ? TRUE : FALSE);};
//update stats after adding or removing a message
//This should only be called by member functions and queue iterators
void UpdateMsgStats(
IN CMsgRef *pmsgref, //Msg that was added/removed
IN BOOL fAdd); //TRUE => message was added
//update stats after adding or removing a message on retry queue
void UpdateRetryStats(
IN BOOL fAdd); //TRUE => message was added
//Returns an approximation of the age of the oldest message in the queue
inline void GetOldestMsg(FILETIME *pft);
//Walk retry queue and remerge messages into normal queues
void MergeRetryQueue();
void SendLinkStateNotification(void);
//Returns TRUE if queue is routed remotely.
BOOL fIsRemote();
//Describes DMQ state. Returned by dwGetDMQState and cached in m_dwFlags
enum
{
DMQ_INVALID = 0x00000001, //This DMQ is no longer valid
DMQ_IN_EMPTY_QUEUE_LIST = 0x00000002, //This DMQ is in empty list
DMQ_SHUTDOWN_SIGNALED = 0x00000004, //Shutdown has been signaled
DMQ_EMPTY = 0x00000010, //DMQ has no messages
DMQ_EXPIRED = 0x00000020, //DMQ has expired in empty list
DMQ_QUEUE_ADMIN_OP_PENDING = 0x00000040, //A queue admin operation is pending
DMQ_UPDATING_OLDEST_TIME = 0x00000100, //Spinlock for updating oldest time
DMQ_CHECK_FOR_STALE_MSGS = 0x00000200, //Do check filehandles during DSN gen
};
//
// Since queues start out empty... there some error paths that can cause a queue
// to be marked as empty, but not actually put in the empty list. We should
// clean these up during reset routes. This tells us if it is safe to do so.
//
BOOL fIsEmptyAndAbandoned()
{
return (!m_aqstats.m_cMsgs &&
!m_dmrq.m_cRetryReferenceCount &&
!m_fqRetryQueue.cGetCount() &&
(m_dwFlags & DMQ_EMPTY) && !(m_dwFlags & DMQ_IN_EMPTY_QUEUE_LIST));
}
public: //IUnknown
STDMETHOD(QueryInterface)(REFIID riid, LPVOID * ppvObj);
STDMETHOD_(ULONG, AddRef)(void) {return CBaseObject::AddRef();};
STDMETHOD_(ULONG, Release)(void) {return CBaseObject::Release();};
public: //IQueueAdminAction
STDMETHOD(HrApplyQueueAdminFunction)(
IQueueAdminMessageFilter *pIQueueAdminMessageFilter);
STDMETHOD(HrApplyActionToMessage)(
IUnknown *pIUnknownMsg,
MESSAGE_ACTION ma,
PVOID pvContext,
BOOL *pfShouldDelete);
STDMETHOD_(BOOL, fMatchesID)
(QUEUELINK_ID *QueueLinkID);
STDMETHOD(QuerySupportedActions)(DWORD *pdwSupportedActions,
DWORD *pdwSupportedFilterFlags)
{
return QueryDefaultSupportedActions(pdwSupportedActions,
pdwSupportedFilterFlags);
};
public: //IQueueAdminQueue
STDMETHOD(HrGetQueueInfo)(
QUEUE_INFO *pliQueueInfo);
STDMETHOD(HrGetQueueID)(
QUEUELINK_ID *pQueueID);
public:
// Return # of failed messages: They are not counted in the m_aqstats of the DMQ
DWORD cGetFailedMsgs() { return m_fqRetryQueue.cGetCount(); }
// Set error code from routing
void SetRoutingDiagnostic(HRESULT hr) { m_hrRoutingDiag = hr; }
protected:
DWORD m_dwSignature;
DWORD m_dwFlags;
LIST_ENTRY m_liDomainEntryDMQs;
//Type of message (as returned by routing) that is on this queue.
CAQMessageType m_aqmt;
DWORD m_cMessageTypeRefs;
IMessageRouter *m_pIMessageRouter;
//Errorcode from routing. This is set to S_OK if there's no errorcode.
//Currently this indicates the reason why a destination is unreachable.
HRESULT m_hrRoutingDiag;
//Members used for DMQ deletion (maintaining a list of empty queues)
LIST_ENTRY m_liEmptyDMQs;
FILETIME m_ftEmptyExpireTime; //expiration time of empty DMQ
DWORD m_cRemovedFromEmptyList; //# of times on list w/o
//being deleted.
CShareLockNH m_slPrivateData; //Share lock to protect access to m_rgpfqQueues
//The following three fields encapsulate all of the routing data
//for this DMQ. The actual routing data is the pointer to the link,
//and the context is used by the link to optimize access
CLinkMsgQueue *m_plmq;
PVOID m_pvLinkContext;
CAQSvrInst *m_paqinst;
//Array of FIFO queues (used to make a priority queue
CFifoQueue<CMsgRef *> *m_rgpfqQueues[NUM_PRIORITIES];
//Retry Qeueue for failed messages
CFifoQueue<CMsgRef *> m_fqRetryQueue;
//class used to store stats
CAQStats m_aqstats;
//which domain is represented in this destination
CDomainMapping m_dmap;
FILETIME m_ftOldest;
CDestMsgRetryQueue m_dmrq;
DWORD m_cCurrentThreadsEnqueuing;
protected: //internal interfaces
//Add Message to front or back of priority queues
HRESULT HrAddMsg(
IN CMsgRef *pmsgref, //Msg to add
IN BOOL fEnqueue, //TRUE => enqueue,FALSE => requeue
IN BOOL fNotify); //TRUE => send notification if needed
void UpdateOldest(FILETIME *pft);
//Callers must use CDestMsgRetryQueueClass
HRESULT HrRetryMsg(IN CMsgRef *pmsgref); //put message on retry queue
friend class CDestMsgRetryQueue;
};
//---[ CDestMsgQueue::HrEnqueueMsg ]-------------------------------------------
//
//
// Description:
// Enqueues a message for remote delivery for a given final destination
// and message type
// Parameters:
// pmsgref AQ Message Reference to enqueue
// fOwnsTypeRef TRUE if this queue is responsible for calling
// IMessageRouter::ReleaseMessageType
// Returns:
// S_OK on success
// Error code from HrAddMsg
// History:
// 5/21/98 - MikeSwa added fOwnsTypeRef
//
//-----------------------------------------------------------------------------
HRESULT CDestMsgQueue::HrEnqueueMsg(IN CMsgRef *pmsgref, BOOL fOwnsTypeRef)
{
HRESULT hr = S_OK;
hr = HrAddMsg(pmsgref, TRUE, TRUE);
if (fOwnsTypeRef && SUCCEEDED(hr))
InterlockedIncrement((PLONG) &m_cMessageTypeRefs);
//Callers should have shutdown lock
_ASSERT(!(m_dwFlags & (DMQ_INVALID | DMQ_SHUTDOWN_SIGNALED)));
return hr;
}
//---[ CDestMsgQueue::paqmtGetMessageType ]------------------------------------
//
//
// Description:
// Get the message type for this queue
// Parameters:
// -
// Returns:
// CAQMessageType * of this queue's message type
// History:
// 5/28/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CAQMessageType *CDestMsgQueue::paqmtGetMessageType()
{
return (&m_aqmt);
}
//---[ CDestMsgQueue::fIsSameMessageType ]-------------------------------------
//
//
// Description:
// Tells if the message type of this queue is the same as the given
// message type.
// Parameters:
// paqmt - ptr to CAQMessageType to test
// Returns:
// TRUE if they match, FALSE if they do not
// History:
// 5/26/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CDestMsgQueue::fIsSameMessageType(CAQMessageType *paqmt)
{
_ASSERT(paqmt);
return m_aqmt.fIsEqual(paqmt);
}
//---[ CDestMsgQueue::pdmqIsSameMessageType ]----------------------------------
//
//
// Description:
// STATIC function used to determine if a LIST_ENTRY refers to a
// CDestMsgQueue with a given message type.
// Parameters:
// paqmt - ptr to CAQMessageType to check against
// pli - ptr to list entry to check (must refer to a CDestMsgQueue)
// Returns:
// Ptr to CDestMsgQueue if LIST_ENTRY refers to a CDestMsgQueue with
// the given message type.
// NULL if no match is not found
// History:
// 5/27/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CDestMsgQueue *CDestMsgQueue::pdmqIsSameMessageType(
CAQMessageType *paqmt,
PLIST_ENTRY pli)
{
CDestMsgQueue *pdmq = NULL;
pdmq = CONTAINING_RECORD(pli, CDestMsgQueue, m_liDomainEntryDMQs);
_ASSERT(DESTMSGQ_SIG == pdmq->m_dwSignature);
//if not the same message type return NULL
if (!pdmq->fIsSameMessageType(paqmt))
pdmq = NULL;
return pdmq;
}
//---[ CDestMsgQueue::pdmqGetDMQFromDomainListEntry ]--------------------------
//
//
// Description:
// Returns the CDestMsgQueue associated with a list entry
// Parameters:
// IN pli ptr to list entry to get CDestMsgQueue from
// Returns:
// ptr to CDestMsgQueue
// History:
// 5/28/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CDestMsgQueue *CDestMsgQueue::pdmqGetDMQFromDomainListEntry(PLIST_ENTRY pli)
{
_ASSERT(DESTMSGQ_SIG == (CONTAINING_RECORD(pli, CDestMsgQueue, m_liDomainEntryDMQs))->m_dwSignature);
return (CONTAINING_RECORD(pli, CDestMsgQueue, m_liDomainEntryDMQs));
}
//---[ CDestMsgQueue::InsertQueueInDomainList ]---------------------------------
//
//
// Description:
// Inserts this CDestMsgQueue into the given linked list of queues
// Parameters:
// pliHead - PLIST_ENTRY for list head
// Returns:
// -
// History:
// 5/27/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::InsertQueueInDomainList(PLIST_ENTRY pliHead)
{
_ASSERT(NULL == m_liDomainEntryDMQs.Flink);
_ASSERT(NULL == m_liDomainEntryDMQs.Blink);
InsertHeadList(pliHead, &m_liDomainEntryDMQs);
}
//---[ CDestMsgQueue::RemoveQueueFromDomainList ]-------------------------------
//
//
// Description:
// Removes this queue from a list of queues
// Parameters:
// -
// Returns:
// -
// History:
// 5/27/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::RemoveQueueFromDomainList()
{
RemoveEntryList(&m_liDomainEntryDMQs);
m_liDomainEntryDMQs.Flink = NULL;
m_liDomainEntryDMQs.Blink = NULL;
}
//---[ CDestMsgQueue::pliGetNextDomainListEntry ]-------------------------------
//
//
// Description:
// Gets the pointer to the next list entry for this queue.
// Parameters:
// -
// Returns:
// The Flink of the queues LIST_ENTRY
// History:
// 6/16/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PLIST_ENTRY CDestMsgQueue::pliGetNextDomainListEntry()
{
return m_liDomainEntryDMQs.Flink;
}
//---[ CDestMsgQueue::InsertQueueInEmptyQueueList ]----------------------------
//
//
// Description:
// Inserts queue at *tail* of DMT empty queue list. The queue that has
// been empty the longest should be at the As with the other EmptyQueue
// list functions this is called by the DMT, when it has the appropriate
// lock for the head of the list.
//
// Upon insertion, an "expire time" is stamped on the queue. If the queue
// is still in the list, then it is a candidate for deletion, and will be
// delete the next time the DMT looks at the queue (everytime HrMapDomain
// is called).
//
// NOTE"We need to make sure this function is thread-safe. Since the
// DMQ lock is aquired exclusively before this is called, we know that
// no one will ENQUEUE a messsage. This function call is tiggered after
// the retry queues are emptied when a connection finished, so we can
// also ensure that no one will call this while there are messages to
// retry.
// It is however (remotely) possible for 2 threads to finish connections
// for this queue and thus cause 2 threads to be in this function.
// The thread that successfully modified the EMPTY bit will be allowed
// to add the queue to the list.
// Parameters:
// IN pliHead The head of the list to insert into
// Returns:
// -
// History:
// 9/11/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::InsertQueueInEmptyQueueList(PLIST_ENTRY pliHead)
{
_ASSERT(m_paqinst);
//Now that we have the exclusive lock recheck to make sure there are no messages
if (m_aqstats.m_cMsgs || m_fqRetryQueue.cGetCount())
return;
//Attempt to set the DMQ_EMPTY bit
if (DMQ_EMPTY & dwInterlockedSetBits(&m_dwFlags, DMQ_EMPTY))
{
//Another thread has set it, we cannot modify the LIST_ENTRY
return;
}
//If it is already in queue, that means that the queue has gone
//from empty to non-empty to empty. Insert at tail of list with new time
if (m_dwFlags & DMQ_IN_EMPTY_QUEUE_LIST)
{
_ASSERT(NULL != m_liEmptyDMQs.Flink);
_ASSERT(NULL != m_liEmptyDMQs.Blink);
RemoveEntryList(&m_liEmptyDMQs);
m_cRemovedFromEmptyList++;
}
else
{
_ASSERT(NULL == m_liEmptyDMQs.Flink);
_ASSERT(NULL == m_liEmptyDMQs.Blink);
}
//Get expire time for this queue
m_paqinst->GetExpireTime(EMPTY_DMQ_EXPIRE_TIME_MINUTES,
&m_ftEmptyExpireTime, NULL);
//Mark queue as in empty queue
dwInterlockedSetBits(&m_dwFlags, DMQ_IN_EMPTY_QUEUE_LIST);
//Insert into queue
InsertTailList(pliHead, &m_liEmptyDMQs);
_ASSERT(pliHead->Blink == &m_liEmptyDMQs);
_ASSERT(!m_aqstats.m_cMsgs); //No other thread should be able to add msgs
}
//---[ DestMsgQueue::RemoveQueueFromEmptyQueueList ]---------------------------
//
//
// Description:
// Removed the queue from the empty list. Caller *must* have DMT lock
// to call this. DMQ will not call this directly, but will call into
// DMT .
// Parameters:
// -
// Returns:
// -
// History:
// 9/11/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::RemoveQueueFromEmptyQueueList()
{
RemoveEntryList(&m_liEmptyDMQs);
//Increment count now that queue is being removed from empty list
m_cRemovedFromEmptyList++;
//Mark queue as not in empty queue
dwInterlockedUnsetBits(&m_dwFlags, DMQ_IN_EMPTY_QUEUE_LIST);
m_liEmptyDMQs.Flink = NULL;
m_liEmptyDMQs.Blink = NULL;
}
//---[ CDestMsgQueue::pliGetNextEmptyQueueListEntry ]--------------------------
//
//
// Description:
// Gets next queue entry in empty list.
// Parameters:
// -
// Returns:
// Next entry pointed to by list entry
// History:
// 9/11/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PLIST_ENTRY CDestMsgQueue::pliGetNextEmptyQueueListEntry()
{
return m_liEmptyDMQs.Flink;
}
//---[ CDestMsgQueue::dwGetDMQState ]------------------------------------------
//
//
// Description:
// Returns the state of the DMQ and caches that state in m_dwFlags. May
// update DMQ_EXPIRED if DMQ is in empty list and it has expired
// Parameters:
// -
// Returns:
// Current DMQ state
// History:
// 9/12/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
DWORD CDestMsgQueue::dwGetDMQState()
{
_ASSERT(DESTMSGQ_SIG == m_dwSignature);
_ASSERT(m_paqinst);
if (DMQ_IN_EMPTY_QUEUE_LIST & m_dwFlags)
{
//If it is empty and not expired..check if expired
if ((DMQ_EMPTY & m_dwFlags) && !(DMQ_EXPIRED & m_dwFlags))
{
if (m_paqinst->fInPast(&m_ftEmptyExpireTime, NULL))
dwInterlockedSetBits(&m_dwFlags, DMQ_EXPIRED);
}
}
return m_dwFlags;
}
//---[ CDestMsgQueue::MarkDMQInvalid ]------------------------------------------
//
//
// Description:
// Marks this queue as invalid. Queue *must* be empty for this to happen
// Parameters:
// -
// Returns:
// -
// History:
// 9/12/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::MarkDMQInvalid()
{
_ASSERT(DESTMSGQ_SIG == m_dwSignature);
_ASSERT(DMQ_EMPTY & m_dwFlags);
dwInterlockedSetBits(&m_dwFlags, DMQ_INVALID);
}
//---[ CDestMsgQueue::pdmqGetDMQFromEmptyListEntry ]---------------------------
//
//
// Description:
// Returns the DMQ corresponding to a given Empty Queue LIST_ENTRY.
//
// Will assert that DMQ signature is valid
// Parameters:
// IN pli Pointer to LIST_ENTRY for queue
// Returns:
//
// History:
// 9/12/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CDestMsgQueue *CDestMsgQueue::pdmqGetDMQFromEmptyListEntry(PLIST_ENTRY pli)
{
_ASSERT(DESTMSGQ_SIG == (CONTAINING_RECORD(pli, CDestMsgQueue, m_liEmptyDMQs))->m_dwSignature);
return (CONTAINING_RECORD(pli, CDestMsgQueue, m_liEmptyDMQs));
}
//---[ CDestMsgQueue::GetDomainMapping ]---------------------------------------
//
//
// Description:
// Returns the domain mapping for this queue.
// Parameters:
// OUT ppdmap Returned domain mapping
// Returns:
// -
// History:
// 9/14/98 - MikeSwa Modified to not have a return value
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::GetDomainMapping(OUT CDomainMapping **ppdmap)
{
_ASSERT(ppdmap);
*ppdmap = &m_dmap;
}
IMessageRouter *CDestMsgQueue::pIMessageRouterGetRouter()
{
return m_pIMessageRouter;
}
//---[ CDestMsgQueue::GetOldestMsg ]-------------------------------------------
//
//
// Description:
// Retruns an approximation of the oldest message in the queue
// Parameters:
// OUT pft FILTIME of "oldest" Messate
// Returns:
// -
// History:
// 12/13/98 - MikeSwa Created
//
//-----------------------------------------------------------------------------
void CDestMsgQueue::GetOldestMsg(FILETIME *pft)
{
_ASSERT(pft);
if (m_aqstats.m_cMsgs)
memcpy(pft, &m_ftOldest, sizeof(FILETIME));
else
ZeroMemory(pft, sizeof (FILETIME));
}
#endif //_DESTMSGQ_H_