1567 lines
33 KiB
C++
1567 lines
33 KiB
C++
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
confpart.cpp
|
|
|
|
Abstract:
|
|
|
|
This module contains implementation of the participant classes.
|
|
|
|
Author:
|
|
|
|
Mu Han (muhan) 15-September-1999
|
|
|
|
--*/
|
|
|
|
#include "stdafx.h"
|
|
#include "confpart.h"
|
|
|
|
#ifdef DEBUG_REFCOUNT
|
|
|
|
ULONG CParticipant::InternalAddRef()
|
|
{
|
|
ULONG lRef = CComObjectRootEx<CComMultiThreadModelNoCS>::InternalAddRef();
|
|
|
|
LOG((MSP_TRACE, "%p, %ws Addref, ref = %d",
|
|
this, (m_InfoItems[0]) ? m_InfoItems[0] : L"new participant", lRef));
|
|
|
|
return lRef;
|
|
}
|
|
|
|
ULONG CParticipant::InternalRelease()
|
|
{
|
|
ULONG lRef = CComObjectRootEx<CComMultiThreadModelNoCS>::InternalRelease();
|
|
|
|
LOG((MSP_TRACE, "%p, %ws Release, ref = %d",
|
|
this, (m_InfoItems[0]) ? m_InfoItems[0] : L"new participant", lRef));
|
|
|
|
return lRef;
|
|
}
|
|
#endif
|
|
|
|
CParticipant::CParticipant()
|
|
: m_pFTM(NULL),
|
|
m_dwSendingMediaTypes(0),
|
|
m_dwReceivingMediaTypes(0)
|
|
{
|
|
// initialize the info item array.
|
|
ZeroMemory(m_InfoItems, sizeof(WCHAR *) * (NUM_SDES_ITEMS));
|
|
}
|
|
|
|
// methods called by the call object.
|
|
HRESULT CParticipant::Init(
|
|
IN WCHAR * szCName,
|
|
IN ITStream * pITStream,
|
|
IN DWORD dwSSRC,
|
|
IN DWORD dwSendRecv,
|
|
IN DWORD dwMediaType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the participant object.
|
|
|
|
Arguments:
|
|
|
|
szCName - the canonical name of the participant.
|
|
|
|
pITStream - the stream that has the participant.
|
|
|
|
dwSSRC - the SSRC of the participant in that stream.
|
|
|
|
dwSendRecv - a sender or a receiver.
|
|
|
|
dwMediaType - the media type of the participant.
|
|
|
|
Return Value:
|
|
|
|
S_OK,
|
|
E_OUTOFMEMORY.
|
|
|
|
--*/
|
|
{
|
|
LOG((MSP_TRACE, "CParticipant::Init, name:%ws", szCName));
|
|
|
|
// create the marshaler.
|
|
HRESULT hr;
|
|
hr = CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pFTM);
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "create marshaler failed, %x", hr));
|
|
return hr;
|
|
}
|
|
|
|
m_InfoItems[0] = (WCHAR *)malloc((lstrlenW(szCName) + 1) * sizeof(WCHAR));
|
|
if (m_InfoItems[0] == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "out of mem for CName"));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
lstrcpyW(m_InfoItems[0], szCName);
|
|
|
|
// add the stream into out list.
|
|
hr = AddStream(pITStream, dwSSRC, dwSendRecv, dwMediaType);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "failed to add stream %x", hr));
|
|
return hr;
|
|
}
|
|
|
|
LOG((MSP_TRACE, "CParticipant: %ws, Init returns S_OK", szCName));
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL CParticipant::UpdateInfo(
|
|
IN int Type,
|
|
IN DWORD dwLen,
|
|
IN WCHAR * szInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Update one item of the participant info.
|
|
|
|
Arguments:
|
|
|
|
Type - the type of the INFO,
|
|
|
|
dwLen - the length of the information.
|
|
|
|
szInfo - the information.
|
|
|
|
Return Value:
|
|
|
|
TRUE - information changed.
|
|
|
|
FALSE - the information is the same, no change was made.
|
|
|
|
--*/
|
|
{
|
|
int index = Type - 1;
|
|
|
|
// if we have an item already, find out if it is the same.
|
|
if (m_InfoItems[index] != NULL)
|
|
{
|
|
if (lstrcmpW(m_InfoItems[index], szInfo) == 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// if the item is new, free the old one
|
|
free(m_InfoItems[index]);
|
|
}
|
|
|
|
// allocate memory and store it.
|
|
m_InfoItems[index] = (WCHAR *)malloc((dwLen + 1) * sizeof(WCHAR));
|
|
if (m_InfoItems[index] == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
lstrcpynW(m_InfoItems[index], szInfo, dwLen);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CParticipant::UpdateSSRC(
|
|
IN ITStream * pITStream,
|
|
IN DWORD dwSSRC,
|
|
IN DWORD dwSendRecv
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Update the SSRC for a stream.
|
|
|
|
Arguments:
|
|
|
|
pITStream - the stream that the participant is on.
|
|
|
|
dwSSRC - the SSRC of the participant.
|
|
|
|
dwSendRecv - the participant is a sender or a receiver.
|
|
|
|
Return Value:
|
|
|
|
TRUE - information changed.
|
|
|
|
FALSE - the stream is not found.
|
|
|
|
--*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
// if the stream is already there, update the SSRC and return.
|
|
int index = m_Streams.Find(pITStream);
|
|
if ( index >= 0)
|
|
{
|
|
m_StreamInfo[index].dwSSRC = dwSSRC;
|
|
m_StreamInfo[index].dwSendRecv |= dwSendRecv;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CParticipant::HasSSRC(
|
|
IN ITStream * pITStream,
|
|
IN DWORD dwSSRC
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
find out if the participant has the SSRC for a stream.
|
|
|
|
Arguments:
|
|
|
|
pITStream - the stream that the participant is on.
|
|
|
|
dwSSRC - the SSRC of the participant.
|
|
|
|
Return Value:
|
|
|
|
TRUE - the SSRC exists.
|
|
|
|
FALSE - the SSRC does not exist.
|
|
|
|
--*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
int index = m_Streams.Find(pITStream);
|
|
if (index >= 0)
|
|
{
|
|
return (m_StreamInfo[index].dwSSRC == dwSSRC);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CParticipant::GetSSRC(
|
|
IN ITStream * pITStream,
|
|
OUT DWORD * pdwSSRC
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Update the SSRC for a stream.
|
|
|
|
Arguments:
|
|
|
|
pITStream - the stream that the participant is on.
|
|
|
|
pdwSSRC - the address to store the SSRC of the participant.
|
|
|
|
Return Value:
|
|
|
|
TRUE - the SSRC is found.
|
|
|
|
FALSE - the SSRC is not found.
|
|
|
|
--*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
// if the stream is already there, update the SSRC and return.
|
|
int index = m_Streams.Find(pITStream);
|
|
if ( index >= 0)
|
|
{
|
|
*pdwSSRC = m_StreamInfo[index].dwSSRC;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
DWORD CParticipant::GetSendRecvStatus(
|
|
IN ITStream * pITStream
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
find out the current send and recv status on a given stream.
|
|
|
|
Arguments:
|
|
|
|
pITStream - the stream that the participant is on.
|
|
|
|
Return Value:
|
|
|
|
A bit mask of send and receive status
|
|
|
|
--*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
int index = m_Streams.Find(pITStream);
|
|
if (index >= 0)
|
|
{
|
|
return m_StreamInfo[index].dwSendRecv;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CParticipant::FinalRelease()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
release everything before being deleted.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG((MSP_TRACE, "CParticipant::FinalRelease, name %ws", m_InfoItems[0]));
|
|
|
|
if (m_pFTM)
|
|
{
|
|
m_pFTM->Release();
|
|
}
|
|
|
|
for (int i = 0; i < NUM_SDES_ITEMS; i ++)
|
|
{
|
|
if (m_InfoItems[i])
|
|
{
|
|
free(m_InfoItems[i]);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < m_Streams.GetSize(); i ++)
|
|
{
|
|
m_Streams[i]->Release();
|
|
}
|
|
m_Streams.RemoveAll();
|
|
|
|
LOG((MSP_TRACE, "CParticipant::FinalRelease - exit"));
|
|
}
|
|
|
|
|
|
// ITParticipant methods, called by the app.
|
|
STDMETHODIMP CParticipant::get_ParticipantTypedInfo(
|
|
IN PARTICIPANT_TYPED_INFO InfoType,
|
|
OUT BSTR * ppInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get a information item for this participant.
|
|
|
|
Arguments:
|
|
|
|
InfoType - The type of the information asked.
|
|
|
|
ppInfo - the mem address to store a BSTR.
|
|
|
|
Return Value:
|
|
|
|
S_OK,
|
|
E_INVALIDARG,
|
|
E_POINTER,
|
|
E_OUTOFMEMORY,
|
|
TAPI_E_NOITEMS
|
|
*/
|
|
{
|
|
LOG((MSP_TRACE, "CParticipant get info, type:%d", InfoType));
|
|
|
|
if (InfoType > PTI_PRIVATE || InfoType < PTI_CANONICALNAME)
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant get info - exit invalid arg"));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (IsBadWritePtr(ppInfo, sizeof(BSTR)))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant get info - exit E_POINTER"));
|
|
return E_POINTER;
|
|
}
|
|
|
|
// check if we have that info.
|
|
CLock lock(m_lock);
|
|
|
|
int index = (int)InfoType;
|
|
if (m_InfoItems[index] == NULL)
|
|
{
|
|
LOG((MSP_INFO, "CParticipant get info - no item for %d", InfoType));
|
|
return TAPI_E_NOITEMS;
|
|
}
|
|
|
|
// make a BSTR out of it.
|
|
BSTR pName = SysAllocString(m_InfoItems[index]);
|
|
|
|
if (pName == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant get info - exit out of mem"));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// return the BSTR.
|
|
*ppInfo = pName;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CParticipant::get_MediaTypes(
|
|
// IN TERMINAL_DIRECTION Direction,
|
|
OUT long * plMediaTypes
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the media type of the participant
|
|
|
|
Arguments:
|
|
|
|
plMediaType - the mem address to store a long.
|
|
|
|
Return Value:
|
|
|
|
S_OK,
|
|
E_POINTER,
|
|
*/
|
|
{
|
|
LOG((MSP_TRACE, "CParticipant::get_MediaTypes - enter"));
|
|
|
|
if (IsBadWritePtr(plMediaTypes, sizeof (long)))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::get_MediaType - exit E_POINTER"));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
CLock lock(m_lock);
|
|
|
|
#if 0
|
|
if (Direction == TD_RENDER)
|
|
{
|
|
*plMediaTypes = (long)m_dwReceivingMediaTypes;
|
|
}
|
|
else
|
|
{
|
|
*plMediaTypes = (long)m_dwSendingMediaTypes;
|
|
}
|
|
#endif
|
|
|
|
*plMediaTypes = (long)(m_dwSendingMediaTypes | m_dwReceivingMediaTypes);
|
|
|
|
LOG((MSP_TRACE, "CParticipant::get_MediaType:%x - exit S_OK", *plMediaTypes));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CParticipant::put_Status(
|
|
IN ITStream * pITStream,
|
|
IN VARIANT_BOOL fEnable
|
|
)
|
|
{
|
|
ENTER_FUNCTION("CParticipant::put_Status");
|
|
LOG((MSP_TRACE, "%s entered. %hs %ws for %p",
|
|
__fxName, fEnable ? "Enable" : "Disable", m_InfoItems[0], pITStream));
|
|
|
|
HRESULT hr;
|
|
|
|
// if the caller specified a stream, find the stream and use it.
|
|
if (pITStream != NULL)
|
|
{
|
|
m_lock.Lock();
|
|
|
|
int index;
|
|
if ((index = m_Streams.Find(pITStream)) < 0)
|
|
{
|
|
m_lock.Unlock();
|
|
|
|
LOG((MSP_ERROR, "%s stream %p not found", __fxName, pITStream));
|
|
|
|
return E_INVALIDARG;
|
|
}
|
|
DWORD dwSSRC = m_StreamInfo[index].dwSSRC;
|
|
|
|
// add ref so that it won't go away.
|
|
pITStream->AddRef();
|
|
|
|
m_lock.Unlock();
|
|
|
|
hr = ((CIPConfMSPStream *)pITStream)->EnableParticipant(
|
|
dwSSRC,
|
|
fEnable
|
|
);
|
|
|
|
pITStream->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
// if the caller didn't specify a stream, set the status on all streams.
|
|
m_lock.Lock();
|
|
int nSize = m_Streams.GetSize();
|
|
ITStream ** Streams = (ITStream **)malloc(sizeof(ITStream*) * nSize);
|
|
|
|
if (Streams == NULL)
|
|
{
|
|
m_lock.Unlock();
|
|
LOG((MSP_ERROR, "%s out of memory", __fxName));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
DWORD * pdwSSRCList = (DWORD *)malloc(sizeof(DWORD) * nSize);
|
|
|
|
if (pdwSSRCList == NULL)
|
|
{
|
|
m_lock.Unlock();
|
|
|
|
free(Streams);
|
|
|
|
LOG((MSP_ERROR, "%s out of memory", __fxName));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
for (int i = 0; i < nSize; i ++)
|
|
{
|
|
Streams[i] = m_Streams[i];
|
|
Streams[i]->AddRef();
|
|
pdwSSRCList[i] = m_StreamInfo[i].dwSSRC;
|
|
}
|
|
m_lock.Unlock();
|
|
|
|
for (i = 0; i < nSize; i ++)
|
|
{
|
|
hr = ((CIPConfMSPStream *)Streams[i])->
|
|
EnableParticipant(pdwSSRCList[i], fEnable);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nSize; i ++)
|
|
{
|
|
Streams[i]->Release();
|
|
}
|
|
|
|
free(Streams);
|
|
free(pdwSSRCList);
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CParticipant::get_Status(
|
|
IN ITStream * pITStream,
|
|
OUT VARIANT_BOOL * pfEnable
|
|
)
|
|
{
|
|
ENTER_FUNCTION("CParticipant::get_Status");
|
|
LOG((MSP_TRACE, "%s entered. %ws %p",
|
|
__fxName, m_InfoItems[0], pITStream));
|
|
|
|
if (IsBadWritePtr(pfEnable, sizeof(VARIANT_BOOL)))
|
|
{
|
|
LOG((MSP_ERROR, "%s bad pointer argument - exit E_POINTER", __fxName));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr;
|
|
BOOL fEnable;
|
|
|
|
// if the caller specified a stream, find the stream and use it.
|
|
if (pITStream != NULL)
|
|
{
|
|
m_lock.Lock();
|
|
|
|
int index;
|
|
if ((index = m_Streams.Find(pITStream)) < 0)
|
|
{
|
|
m_lock.Unlock();
|
|
|
|
LOG((MSP_ERROR, "%s stream %p not found", __fxName, pITStream));
|
|
|
|
return E_INVALIDARG;
|
|
}
|
|
DWORD dwSSRC = m_StreamInfo[index].dwSSRC;
|
|
|
|
// add ref so that it won't go away.
|
|
pITStream->AddRef();
|
|
|
|
m_lock.Unlock();
|
|
|
|
hr = ((CIPConfMSPStream *)pITStream)->GetParticipantStatus(
|
|
dwSSRC,
|
|
&fEnable
|
|
);
|
|
|
|
pITStream->Release();
|
|
|
|
*pfEnable = (fEnable) ? VARIANT_TRUE : VARIANT_FALSE;
|
|
|
|
return hr;
|
|
}
|
|
|
|
// if the caller didn't specify a stream, get the status from all streams.
|
|
m_lock.Lock();
|
|
int nSize = m_Streams.GetSize();
|
|
ITStream ** Streams = (ITStream **)malloc(sizeof(ITStream*) * nSize);
|
|
|
|
if (Streams == NULL)
|
|
{
|
|
m_lock.Unlock();
|
|
LOG((MSP_ERROR, "%s out of memory", __fxName));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
DWORD * pdwSSRCList = (DWORD *)malloc(sizeof(DWORD) * nSize);
|
|
|
|
if (pdwSSRCList == NULL)
|
|
{
|
|
m_lock.Unlock();
|
|
|
|
free(Streams);
|
|
|
|
LOG((MSP_ERROR, "%s out of memory", __fxName));
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
for (int i = 0; i < nSize; i ++)
|
|
{
|
|
Streams[i] = m_Streams[i];
|
|
Streams[i]->AddRef();
|
|
pdwSSRCList[i] = m_StreamInfo[i].dwSSRC;
|
|
}
|
|
m_lock.Unlock();
|
|
|
|
fEnable = FALSE;
|
|
|
|
for (i = 0; i < nSize; i ++)
|
|
{
|
|
BOOL fEnabledOnStream;
|
|
hr = ((CIPConfMSPStream *)Streams[i])->
|
|
GetParticipantStatus(pdwSSRCList[i], &fEnabledOnStream);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// as long as it is enabled on one stream, it is enabled.
|
|
fEnable = fEnable || fEnabledOnStream;
|
|
}
|
|
|
|
for (i = 0; i < nSize; i ++)
|
|
{
|
|
Streams[i]->Release();
|
|
}
|
|
|
|
free(Streams);
|
|
free(pdwSSRCList);
|
|
|
|
*pfEnable = (fEnable) ? VARIANT_TRUE : VARIANT_FALSE;
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CParticipant::EnumerateStreams(
|
|
OUT IEnumStream ** ppEnumStream
|
|
)
|
|
{
|
|
LOG((MSP_TRACE,
|
|
"EnumerateStreams entered. ppEnumStream:%x", ppEnumStream));
|
|
|
|
//
|
|
// Check parameters.
|
|
//
|
|
|
|
if (IsBadWritePtr(ppEnumStream, sizeof(VOID *)))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::EnumerateStreams - "
|
|
"bad pointer argument - exit E_POINTER"));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
//
|
|
// First see if this call has been shut down.
|
|
// acquire the lock before accessing the stream object list.
|
|
//
|
|
|
|
CLock lock(m_lock);
|
|
|
|
if (m_Streams.GetData() == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::EnumerateStreams - "
|
|
"call appears to have been shut down - exit E_UNEXPECTED"));
|
|
|
|
// This call has been shut down.
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
//
|
|
// Create an enumerator object.
|
|
//
|
|
typedef _CopyInterface<ITStream> CCopy;
|
|
typedef CSafeComEnum<IEnumStream, &__uuidof(IEnumStream),
|
|
ITStream *, CCopy> CEnumerator;
|
|
|
|
HRESULT hr;
|
|
|
|
CComObject<CEnumerator> *pEnum = NULL;
|
|
|
|
hr = ::CreateCComObjectInstance(&pEnum);
|
|
|
|
if (pEnum == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::EnumerateStreams - "
|
|
"Could not create enumerator object, %x", hr));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// query for the __uuidof(IEnumStream) i/f
|
|
//
|
|
|
|
hr = pEnum->_InternalQueryInterface(__uuidof(IEnumStream), (void**)ppEnumStream);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::EnumerateStreams - "
|
|
"query enum interface failed, %x", hr));
|
|
|
|
delete pEnum;
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Init the enumerator object. The CSafeComEnum can handle zero-sized array.
|
|
//
|
|
|
|
hr = pEnum->Init(
|
|
m_Streams.GetData(), // the begin itor
|
|
m_Streams.GetData() + m_Streams.GetSize(), // the end itor,
|
|
NULL, // IUnknown
|
|
AtlFlagCopy // copy the data.
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::EnumerateStreams - "
|
|
"init enumerator object failed, %x", hr));
|
|
|
|
(*ppEnumStream)->Release();
|
|
return hr;
|
|
}
|
|
|
|
LOG((MSP_TRACE, "CParticipant::EnumerateStreams - exit S_OK"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CParticipant::get_Streams(
|
|
OUT VARIANT * pVariant
|
|
)
|
|
{
|
|
LOG((MSP_TRACE, "CParticipant::get_Streams - enter"));
|
|
|
|
//
|
|
// Check parameters.
|
|
//
|
|
|
|
if ( IsBadWritePtr(pVariant, sizeof(VARIANT) ) )
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::get_Streams - "
|
|
"bad pointer argument - exit E_POINTER"));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
//
|
|
// See if this call has been shut down. Acquire the lock before accessing
|
|
// the stream object list.
|
|
//
|
|
|
|
CLock lock(m_lock);
|
|
|
|
if (m_Streams.GetData() == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::get_Streams - "
|
|
"call appears to have been shut down - exit E_UNEXPECTED"));
|
|
|
|
// This call has been shut down.
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
//
|
|
// create the collection object - see mspcoll.h
|
|
//
|
|
typedef CTapiIfCollection< ITStream * > StreamCollection;
|
|
CComObject<StreamCollection> * pCollection;
|
|
|
|
HRESULT hr;
|
|
|
|
hr = ::CreateCComObjectInstance(&pCollection);
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::get_Streams - "
|
|
"can't create collection - exit 0x%08x", hr));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// get the Collection's IDispatch interface
|
|
//
|
|
|
|
IDispatch * pDispatch;
|
|
|
|
hr = pCollection->_InternalQueryInterface(__uuidof(IDispatch),
|
|
(void **) &pDispatch );
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::get_Streams - "
|
|
"QI for IDispatch on collection failed - exit 0x%08x", hr));
|
|
|
|
delete pCollection;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Init the collection using an iterator -- pointers to the beginning and
|
|
// the ending element plus one.
|
|
//
|
|
|
|
hr = pCollection->Initialize( m_Streams.GetSize(),
|
|
m_Streams.GetData(),
|
|
m_Streams.GetData() + m_Streams.GetSize() );
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipant::get_Streams - "
|
|
"Initialize on collection failed - exit 0x%08x", hr));
|
|
|
|
pDispatch->Release();
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// put the IDispatch interface pointer into the variant
|
|
//
|
|
|
|
LOG((MSP_INFO, "CParticipant::get_Streams - "
|
|
"placing IDispatch value %08x in variant", pDispatch));
|
|
|
|
VariantInit(pVariant);
|
|
pVariant->vt = VT_DISPATCH;
|
|
pVariant->pdispVal = pDispatch;
|
|
|
|
LOG((MSP_TRACE, "CParticipant::get_Streams - exit S_OK"));
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CParticipant::AddStream(
|
|
IN ITStream * pITStream,
|
|
IN DWORD dwSSRC,
|
|
IN DWORD dwSendRecv,
|
|
IN DWORD dwMediaType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A participant might appear on more than one streams. This function adds
|
|
a new stream and the SSRC into the participant's list.
|
|
|
|
Arguments:
|
|
|
|
pITStream - the stream that has the participant.
|
|
|
|
dwSSRC - the SSRC of the participant in that stream.
|
|
|
|
dwSendRecv - the participant is a sender or receiver in the stream.
|
|
|
|
dwMediaType - the media type of the stream.
|
|
|
|
Return Value:
|
|
|
|
S_OK,
|
|
E_OUTOFMEMORY,
|
|
*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
// if the stream is already there, update the SSRC and return.
|
|
int index = m_Streams.Find(pITStream);
|
|
if ( index >= 0)
|
|
{
|
|
m_StreamInfo[index].dwSSRC = dwSSRC;
|
|
m_StreamInfo[index].dwSendRecv |= dwSendRecv;
|
|
return S_OK;
|
|
}
|
|
|
|
// add the stream.
|
|
if (!m_Streams.Add(pITStream))
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
// add the SSRC and sender flag.
|
|
STREAM_INFO Info;
|
|
Info.dwSSRC = dwSSRC;
|
|
Info.dwSendRecv = dwSendRecv;
|
|
Info.dwState = PESTREAM_RECOVER;
|
|
|
|
if (!m_StreamInfo.Add(Info))
|
|
{
|
|
m_Streams.Remove(pITStream);
|
|
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
pITStream->AddRef();
|
|
|
|
// update the mediatype.
|
|
if (dwSendRecv & PART_SEND)
|
|
{
|
|
m_dwSendingMediaTypes |= dwMediaType;
|
|
}
|
|
if (dwSendRecv & PART_RECV)
|
|
{
|
|
m_dwReceivingMediaTypes |= dwMediaType;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CParticipant::RemoveStream(
|
|
IN ITStream * pITStream,
|
|
IN DWORD dwSSRC,
|
|
OUT BOOL * pbLast
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A participant might appear on more than one streams. This function remove
|
|
a stream from the participant's list.
|
|
|
|
Arguments:
|
|
|
|
pITStream - the stream that has the participant.
|
|
|
|
dwSSRC - the SSRC of the participant in that stream.
|
|
|
|
pbLast - the memory space to store a boolean value, specifying if the
|
|
stream removed was the last one in the list.
|
|
|
|
Return Value:
|
|
|
|
S_OK,
|
|
E_POINTER,
|
|
*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
// first find the stream.
|
|
int index = m_Streams.Find(pITStream);
|
|
|
|
if (index < 0)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (m_Streams.GetSize() != m_StreamInfo.GetSize())
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// then check the SSRC.
|
|
if (m_StreamInfo[index].dwSSRC != dwSSRC)
|
|
{
|
|
// this is not the participant being looking for.
|
|
return E_FAIL;
|
|
}
|
|
|
|
// SSRC match, we found the participant. remove the stream and info.
|
|
m_Streams.RemoveAt(index);
|
|
m_StreamInfo.RemoveAt(index);
|
|
|
|
// release the refcount we had in the list.
|
|
pITStream->Release();
|
|
|
|
// recalculate the media types.
|
|
m_dwSendingMediaTypes = 0;
|
|
m_dwReceivingMediaTypes = 0;
|
|
|
|
for (int i = 0; i < m_Streams.GetSize(); i ++)
|
|
{
|
|
if (m_StreamInfo[i].dwSendRecv & PART_SEND)
|
|
{
|
|
m_dwSendingMediaTypes |= ((CIPConfMSPStream *)m_Streams[i])->MediaType();
|
|
}
|
|
|
|
if (m_StreamInfo[i].dwSendRecv & PART_RECV)
|
|
{
|
|
m_dwReceivingMediaTypes |= ((CIPConfMSPStream *)m_Streams[i])->MediaType();
|
|
}
|
|
}
|
|
|
|
*pbLast = (m_Streams.GetSize() == 0);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CParticipant::SetStreamState (
|
|
IN ITStream * pITStream,
|
|
IN PESTREAM_STATE state
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the state on stream.
|
|
|
|
--*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
// first find the stream.
|
|
int index = m_Streams.Find(pITStream);
|
|
if (index < 0)
|
|
return E_FAIL;
|
|
|
|
if (m_Streams.GetSize() != m_StreamInfo.GetSize())
|
|
return E_UNEXPECTED;
|
|
|
|
DWORD dw = m_StreamInfo[index].dwState;
|
|
|
|
switch (state)
|
|
{
|
|
case PESTREAM_RECOVER:
|
|
// set recover
|
|
dw |= PESTREAM_RECOVER;
|
|
// clear timeout bit
|
|
dw |= PESTREAM_TIMEOUT;
|
|
dw &= (PESTREAM_TIMEOUT ^ PESTREAM_FULLBITS);
|
|
break;
|
|
|
|
case PESTREAM_TIMEOUT:
|
|
// set timeout
|
|
dw |= PESTREAM_TIMEOUT;
|
|
// clear recover bit
|
|
dw |= PESTREAM_RECOVER;
|
|
dw &= (PESTREAM_RECOVER ^ PESTREAM_FULLBITS);
|
|
break;
|
|
|
|
default:
|
|
LOG ((MSP_ERROR, "unknown stream state. %x", state));
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_StreamInfo[index].dwState = dw;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CParticipant::GetStreamState (
|
|
IN ITStream * pITStream,
|
|
OUT DWORD * pdwState
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the state on stream.
|
|
|
|
--*/
|
|
{
|
|
CLock lock(m_lock);
|
|
|
|
// first find the stream.
|
|
int index = m_Streams.Find(pITStream);
|
|
if (index < 0)
|
|
return E_FAIL;
|
|
|
|
if (m_Streams.GetSize() != m_StreamInfo.GetSize())
|
|
return E_UNEXPECTED;
|
|
|
|
*pdwState = m_StreamInfo[index].dwState;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
INT CParticipant::GetStreamCount (DWORD dwSendRecv)
|
|
{
|
|
// this is called by ourself
|
|
_ASSERTE ((dwSendRecv & PART_SEND) || (dwSendRecv & PART_RECV));
|
|
|
|
CLock lock(m_lock);
|
|
|
|
int i, count = 0;
|
|
|
|
for (i=0; i<m_StreamInfo.GetSize (); i++)
|
|
{
|
|
if (m_StreamInfo[i].dwSendRecv & dwSendRecv)
|
|
count ++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
INT CParticipant::GetStreamTimeOutCount (DWORD dwSendRecv)
|
|
{
|
|
// this is called by ourself
|
|
_ASSERTE ((dwSendRecv & PART_SEND) || (dwSendRecv & PART_RECV));
|
|
|
|
CLock lock(m_lock);
|
|
|
|
int i, count = 0;
|
|
|
|
for (i=0; i<m_StreamInfo.GetSize (); i++)
|
|
{
|
|
if ((m_StreamInfo[i].dwSendRecv & dwSendRecv) &&
|
|
(m_StreamInfo[i].dwState & PESTREAM_TIMEOUT))
|
|
count ++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
BOOL CParticipantList::FindByCName(WCHAR *szCName, int *pIndex) const
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Find a participant by its canonical name. If the function returns true,
|
|
*pIndex contains the index of the participant. If the function returns
|
|
false, *pIndex contains the index where the new participant should be
|
|
inserted.
|
|
|
|
Arguments:
|
|
|
|
szCName - the canonical name of the participant.
|
|
|
|
pIndex - the memory address to store an integer.
|
|
|
|
Return Value:
|
|
|
|
TRUE - the participant is found.
|
|
|
|
FALSE - the participant is not in the list.
|
|
*/
|
|
{
|
|
for(int i = 0; i < m_nSize; i++)
|
|
{
|
|
// This list is an ordered list based on dictionary order. We are using
|
|
// a linear search here, it could be changed to a binary search.
|
|
|
|
// CompareCName will return 0 if the name is the same, <0 if the szCName
|
|
// is bigger, >0 if the szCName is smaller.
|
|
int res = ((CParticipant *)m_aT[i])->CompareCName(szCName);
|
|
if(res >= 0)
|
|
{
|
|
*pIndex = i;
|
|
return (res == 0);
|
|
}
|
|
}
|
|
*pIndex = m_nSize;
|
|
return FALSE; // not found
|
|
}
|
|
|
|
BOOL CParticipantList::InsertAt(int nIndex, ITParticipant *pITParticipant)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Insert a participant into the list at a given index.
|
|
|
|
Arguments:
|
|
|
|
nIndex - the location where the new object is inserted.
|
|
|
|
pITParticipant - the object to be inserted.
|
|
|
|
Return Value:
|
|
|
|
TRUE - the participant is inserted.
|
|
|
|
FALSE - out of memory.
|
|
*/
|
|
{
|
|
_ASSERTE(nIndex >= 0 && nIndex <= m_nSize);
|
|
if(m_nSize == m_nAllocSize)
|
|
{
|
|
if (!Grow()) return FALSE;
|
|
}
|
|
|
|
memmove((void*)&m_aT[nIndex+1], (void*)&m_aT[nIndex],
|
|
(m_nSize - nIndex) * sizeof(ITParticipant *));
|
|
|
|
m_nSize++;
|
|
|
|
SetAtIndex(nIndex, pITParticipant);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CParticipantEvent::CParticipantEvent()
|
|
: m_pFTM(NULL),
|
|
m_pITParticipant(NULL),
|
|
m_pITSubStream(NULL),
|
|
m_Event(PE_NEW_PARTICIPANT)
|
|
{}
|
|
|
|
// methods called by the call object.
|
|
HRESULT CParticipantEvent::Init(
|
|
IN PARTICIPANT_EVENT Event,
|
|
IN ITParticipant * pITParticipant,
|
|
IN ITSubStream * pITSubStream
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the ParticipantEvent object.
|
|
|
|
Arguments:
|
|
|
|
Event - the event.
|
|
|
|
pITParticipant - the participant.
|
|
|
|
pITSubStream - the substream, can be NULL.
|
|
|
|
Return Value:
|
|
|
|
S_OK,
|
|
|
|
--*/
|
|
{
|
|
LOG((MSP_TRACE, "CParticipantEvent::Init"));
|
|
|
|
// create the marshaler.
|
|
HRESULT hr;
|
|
hr = CoCreateFreeThreadedMarshaler(GetControllingUnknown(), &m_pFTM);
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "create marshaler failed, %x", hr));
|
|
return hr;
|
|
}
|
|
|
|
m_Event = Event;
|
|
|
|
m_pITParticipant = pITParticipant;
|
|
if (m_pITParticipant) m_pITParticipant->AddRef();
|
|
|
|
m_pITSubStream = pITSubStream;
|
|
if (m_pITSubStream) m_pITSubStream->AddRef();
|
|
|
|
LOG((MSP_TRACE, "CParticipantEvent Init returns S_OK"));
|
|
return S_OK;
|
|
}
|
|
|
|
void CParticipantEvent::FinalRelease()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
release everything before being deleted.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LOG((MSP_TRACE, "CParticipantEvent::FinalRelease - enter"));
|
|
|
|
if (m_pFTM)
|
|
{
|
|
m_pFTM->Release();
|
|
}
|
|
|
|
if (m_pITParticipant) m_pITParticipant->Release();
|
|
|
|
if (m_pITSubStream) m_pITSubStream->Release();
|
|
|
|
LOG((MSP_TRACE, "CParticipantEvent::FinalRelease - exit"));
|
|
}
|
|
|
|
STDMETHODIMP CParticipantEvent::get_Event(
|
|
OUT PARTICIPANT_EVENT * pParticipantEvent
|
|
)
|
|
{
|
|
if (IsBadWritePtr(pParticipantEvent, sizeof (PARTICIPANT_EVENT)))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipantEvent::get_Event - exit E_POINTER"));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
*pParticipantEvent = m_Event;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CParticipantEvent::get_Participant(
|
|
OUT ITParticipant ** ppITParticipant
|
|
)
|
|
{
|
|
if (IsBadWritePtr(ppITParticipant, sizeof (void *)))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipantEvent::get_participant - exit E_POINTER"));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (!m_pITParticipant)
|
|
{
|
|
// LOG((MSP_ERROR, "CParticipantevnt::get_Participant - exit no item"));
|
|
return TAPI_E_NOITEMS;
|
|
}
|
|
|
|
m_pITParticipant->AddRef();
|
|
*ppITParticipant = m_pITParticipant;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CParticipantEvent::get_SubStream(
|
|
OUT ITSubStream** ppSubStream
|
|
)
|
|
{
|
|
if (IsBadWritePtr(ppSubStream, sizeof (void *)))
|
|
{
|
|
LOG((MSP_ERROR, "CParticipantEvent::get_SubStream - exit E_POINTER"));
|
|
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (!m_pITSubStream)
|
|
{
|
|
LOG((MSP_WARN, "CParticipantevnt::get_SubStream - exit no item"));
|
|
return TAPI_E_NOITEMS;
|
|
}
|
|
|
|
m_pITSubStream->AddRef();
|
|
*ppSubStream = m_pITSubStream;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CreateParticipantEvent(
|
|
IN PARTICIPANT_EVENT Event,
|
|
IN ITParticipant * pITParticipant,
|
|
IN ITSubStream * pITSubStream,
|
|
OUT IDispatch ** ppIDispatch
|
|
)
|
|
{
|
|
// create the object.
|
|
CComObject<CParticipantEvent> * pCOMParticipantEvent;
|
|
|
|
HRESULT hr;
|
|
|
|
hr = ::CreateCComObjectInstance(&pCOMParticipantEvent);
|
|
|
|
if (NULL == pCOMParticipantEvent)
|
|
{
|
|
LOG((MSP_ERROR, "could not create participant event:%x", hr));
|
|
return hr;
|
|
}
|
|
|
|
IDispatch * pIDispatch;
|
|
|
|
// get the interface pointer.
|
|
hr = pCOMParticipantEvent->_InternalQueryInterface(
|
|
__uuidof(IDispatch),
|
|
(void **)&pIDispatch
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "Create ParticipantEvent QueryInterface failed: %x", hr));
|
|
delete pCOMParticipantEvent;
|
|
return hr;
|
|
}
|
|
|
|
// Initialize the object.
|
|
hr = pCOMParticipantEvent->Init(
|
|
Event,
|
|
pITParticipant,
|
|
pITSubStream
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CreateMSPParticipantEvent:call init failed: %x", hr));
|
|
pIDispatch->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
*ppIDispatch = pIDispatch;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CreateParticipantEnumerator(
|
|
IN ITParticipant ** begin,
|
|
IN ITParticipant ** end,
|
|
OUT IEnumParticipant ** ppEnumParticipant
|
|
)
|
|
{
|
|
//
|
|
// Create an enumerator object.
|
|
//
|
|
|
|
typedef _CopyInterface<ITParticipant> CCopy;
|
|
typedef CSafeComEnum<IEnumParticipant, &__uuidof(IEnumParticipant),
|
|
ITParticipant *, CCopy> CEnumerator;
|
|
|
|
HRESULT hr;
|
|
|
|
CComObject<CEnumerator> *pEnum = NULL;
|
|
|
|
hr = ::CreateCComObjectInstance(&pEnum);
|
|
|
|
if (pEnum == NULL)
|
|
{
|
|
LOG((MSP_ERROR, "CreateParticipantEnumerator - "
|
|
"Could not create enumerator object, %x", hr));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// query for the __uuidof(IEnumParticipant) i/f
|
|
//
|
|
|
|
hr = pEnum->_InternalQueryInterface(
|
|
__uuidof(IEnumParticipant),
|
|
(void**)ppEnumParticipant
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CreateParticipantEnumerator - "
|
|
"query enum interface failed, %x", hr));
|
|
|
|
delete pEnum;
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Init the enumerator object. The CSafeComEnum can handle zero-sized array.
|
|
//
|
|
|
|
hr = pEnum->Init(
|
|
begin, // the begin itor
|
|
end, // the end itor,
|
|
NULL, // IUnknown
|
|
AtlFlagCopy // copy the data.
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CreateParticipantEnumerator - "
|
|
"init enumerator object failed, %x", hr));
|
|
|
|
(*ppEnumParticipant)->Release();
|
|
return hr;
|
|
}
|
|
|
|
LOG((MSP_TRACE, "CreateParticipantEnumerator - exit S_OK"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CreateParticipantCollection(
|
|
IN ITParticipant ** begin,
|
|
IN ITParticipant ** end,
|
|
IN int nSize,
|
|
OUT VARIANT * pVariant
|
|
)
|
|
{
|
|
//
|
|
// create the collection object - see mspcoll.h
|
|
//
|
|
|
|
typedef CTapiIfCollection< ITParticipant * > ParticipantCollection;
|
|
CComObject<ParticipantCollection> * pCollection;
|
|
|
|
HRESULT hr;
|
|
|
|
hr = ::CreateCComObjectInstance(&pCollection);
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
LOG((MSP_ERROR, "CreateParticipantCollection - "
|
|
"can't create collection - exit 0x%08x", hr));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// get the Collection's IDispatch interface
|
|
//
|
|
|
|
IDispatch * pDispatch;
|
|
|
|
hr = pCollection->_InternalQueryInterface(__uuidof(IDispatch),
|
|
(void **) &pDispatch );
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
LOG((MSP_ERROR, "CreateParticipantCollection - "
|
|
"QI for IDispatch on collection failed - exit 0x%08x", hr));
|
|
|
|
delete pCollection;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Init the collection using an iterator -- pointers to the beginning and
|
|
// the ending element plus one.
|
|
//
|
|
|
|
hr = pCollection->Initialize(nSize, begin, end);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LOG((MSP_ERROR, "CreateParticipantCollection- "
|
|
"Initialize on collection failed - exit 0x%08x", hr));
|
|
|
|
pDispatch->Release();
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// put the IDispatch interface pointer into the variant
|
|
//
|
|
|
|
VariantInit(pVariant);
|
|
pVariant->vt = VT_DISPATCH;
|
|
pVariant->pdispVal = pDispatch;
|
|
|
|
LOG((MSP_TRACE, "CreateParticipantCollection - exit S_OK"));
|
|
|
|
return S_OK;
|
|
}
|
|
|