summaryrefslogtreecommitdiff
path: root/src/vm/comconnectionpoints.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/comconnectionpoints.cpp')
-rw-r--r--src/vm/comconnectionpoints.cpp1308
1 files changed, 1308 insertions, 0 deletions
diff --git a/src/vm/comconnectionpoints.cpp b/src/vm/comconnectionpoints.cpp
new file mode 100644
index 0000000000..a92ac2dc29
--- /dev/null
+++ b/src/vm/comconnectionpoints.cpp
@@ -0,0 +1,1308 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// ===========================================================================
+// File: ComConnectionPoints.cpp
+//
+
+// ===========================================================================
+// Implementation of the classes used to expose connection points to COM.
+// ===========================================================================
+
+
+#include "common.h"
+
+#include "comconnectionpoints.h"
+#include "comcallablewrapper.h"
+
+//------------------------------------------------------------------------------------------
+// Implementation of helper class used to expose connection points
+//------------------------------------------------------------------------------------------
+
+void ConnectionPoint::Advise_Wrapper(LPVOID ptr)
+{
+ WRAPPER_NO_CONTRACT;
+
+ Advise_Args *pArgs = (Advise_Args *)ptr;
+ pArgs->pThis->AdviseWorker(pArgs->pUnk, pArgs->pdwCookie);
+}
+
+void ConnectionPoint::Unadvise_Wrapper(LPVOID ptr)
+{
+ WRAPPER_NO_CONTRACT;
+
+ Unadvise_Args *pArgs = (Unadvise_Args *)ptr;
+ pArgs->pThis->UnadviseWorker(pArgs->dwCookie);
+}
+
+void ConnectionPoint::GetConnectionPointContainer_Wrapper(LPVOID ptr)
+{
+ WRAPPER_NO_CONTRACT;
+
+ GetConnectionPointContainer_Args *pArgs = (GetConnectionPointContainer_Args *)ptr;
+ *pArgs->ppCPC = pArgs->pThis->GetConnectionPointContainerWorker();
+}
+
+ConnectionPoint::ConnectionPoint(ComCallWrapper *pWrap, MethodTable *pEventMT)
+: m_pOwnerWrap(pWrap)
+, m_pTCEProviderMT(pWrap->GetSimpleWrapper()->GetMethodTable())
+, m_pEventItfMT(pEventMT)
+, m_Lock(CrstInterop)
+, m_cbRefCount(0)
+, m_apEventMethods(NULL)
+, m_NumEventMethods(0)
+, m_pLastInserted(NULL)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(pWrap));
+ PRECONDITION(CheckPointer(pEventMT));
+ }
+ CONTRACTL_END;
+
+ // Retrieve the connection IID.
+ pEventMT->GetGuid(&m_rConnectionIID, TRUE);
+
+ // Set up the event methods.
+ SetupEventMethods();
+}
+
+ConnectionPoint::~ConnectionPoint()
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (m_apEventMethods)
+ delete []m_apEventMethods;
+}
+
+HRESULT __stdcall ConnectionPoint::QueryInterface(REFIID riid, void** ppv)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppv, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!ppv)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppv = NULL;
+
+ SetupForComCallHR();
+
+ if (riid == IID_IConnectionPoint)
+ {
+ *ppv = static_cast<IConnectionPoint*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppv = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+
+ ULONG cbRef = SafeAddRefPreemp((IUnknown*)*ppv);
+ //@TODO(CLE) AddRef logging that doesn't use QI
+
+ return S_OK;
+}
+
+ULONG __stdcall ConnectionPoint::AddRef()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ // The connection point objects share the CCW's ref count.
+ return m_pOwnerWrap->AddRef();
+}
+
+ULONG __stdcall ConnectionPoint::Release()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ HRESULT hr = S_OK;
+ ULONG cbRef = -1;
+
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ // The connection point objects share the CCW's ref count.
+ cbRef = m_pOwnerWrap->Release();
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return cbRef;
+}
+
+HRESULT __stdcall ConnectionPoint::GetConnectionInterface(IID *pIID)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(pIID, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!pIID)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *pIID = GUID_NULL;
+
+ SetupForComCallHR();
+
+ *pIID = m_rConnectionIID;
+ return S_OK;
+}
+
+HRESULT __stdcall ConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer **ppCPC)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppCPC, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // Verify the arguments.
+ if (!ppCPC)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppCPC = NULL;
+
+ SetupForComCallHR();
+
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ GCX_COOP_THREAD_EXISTS(GET_THREAD());
+ Thread *pThread = GET_THREAD();
+
+ ADID targetADID;
+ Context *pTargetContext;
+ if (m_pOwnerWrap->NeedToSwitchDomains(pThread, &targetADID, &pTargetContext))
+ {
+ GetConnectionPointContainer_Args args = {this, ppCPC};
+ pThread->DoContextCallBack(targetADID, pTargetContext, GetConnectionPointContainer_Wrapper, &args);
+ }
+ else
+ {
+ *ppCPC = GetConnectionPointContainerWorker();
+ }
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+}
+
+HRESULT __stdcall ConnectionPoint::Advise(IUnknown *pUnk, DWORD *pdwCookie)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(pUnk, NULL_OK));
+ PRECONDITION(CheckPointer(pdwCookie, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // Verify the arguments.
+ if (!pUnk || !pdwCookie)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *pdwCookie = NULL;
+
+ SetupForComCallHR();
+
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ GCX_COOP_THREAD_EXISTS(GET_THREAD());
+ Thread *pThread = GET_THREAD();
+
+ ADID targetADID;
+ Context *pTargetContext;
+ if (m_pOwnerWrap->NeedToSwitchDomains(pThread, &targetADID, &pTargetContext))
+ {
+ Advise_Args args = {this, pUnk, pdwCookie};
+ pThread->DoContextCallBack(targetADID, pTargetContext, Advise_Wrapper, &args);
+ }
+ else
+ {
+ AdviseWorker(pUnk, pdwCookie);
+ }
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+}
+
+HRESULT __stdcall ConnectionPoint::Unadvise(DWORD dwCookie)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // Verify the arguments.
+ if (dwCookie == 0)
+ return CONNECT_E_NOCONNECTION;
+
+ SetupForComCallHR();
+
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ GCX_COOP_THREAD_EXISTS(GET_THREAD());
+ Thread *pThread = GET_THREAD();
+
+ ADID targetADID;
+ Context *pTargetContext;
+ if (m_pOwnerWrap->NeedToSwitchDomains(pThread, &targetADID, &pTargetContext))
+ {
+ Unadvise_Args args = {this, dwCookie};
+ pThread->DoContextCallBack(targetADID, pTargetContext, Unadvise_Wrapper, &args);
+ }
+ else
+ {
+ UnadviseWorker(dwCookie);
+ }
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+}
+
+HRESULT __stdcall ConnectionPoint::EnumConnections(IEnumConnections **ppEnum)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppEnum, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!ppEnum)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppEnum = NULL;
+
+ SetupForComCallHR();
+
+ ConnectionEnum *pConEnum = new(nothrow) ConnectionEnum(this);
+ if (!pConEnum)
+ return E_OUTOFMEMORY;
+
+ // Retrieve the IEnumConnections interface. This cannot fail.
+ HRESULT hr = SafeQueryInterfacePreemp((IUnknown*)pConEnum, IID_IEnumConnections, (IUnknown**)ppEnum);
+ LogInteropQI((IUnknown*)pConEnum, IID_IEnumConnections, hr, "ConnectionPoint::EnumConnections: QIing for IID_IEnumConnections");
+ _ASSERTE(hr == S_OK);
+
+ return hr;
+}
+
+IConnectionPointContainer *ConnectionPoint::GetConnectionPointContainerWorker()
+{
+ CONTRACT(IConnectionPointContainer*)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ POSTCONDITION(CheckPointer(RETVAL));
+ }
+ CONTRACT_END;
+
+ // Retrieve the IConnectionPointContainer from the owner wrapper.
+ RETURN (IConnectionPointContainer*)
+ ComCallWrapper::GetComIPFromCCW(m_pOwnerWrap, IID_IConnectionPointContainer, NULL);
+}
+
+void ConnectionPoint::AdviseWorker(IUnknown *pUnk, DWORD *pdwCookie)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ PRECONDITION(CheckPointer(pUnk));
+ PRECONDITION(CheckPointer(pdwCookie));
+ }
+ CONTRACTL_END;
+
+ SafeComHolder<IUnknown> pEventItf = NULL;
+ HRESULT hr;
+
+ // Make sure we have a pointer to the interface and not to another IUnknown.
+ hr = SafeQueryInterface(pUnk, m_rConnectionIID, &pEventItf );
+ LogInteropQI(pUnk, m_rConnectionIID, hr, "ConnectionPoint::AdviseWorker: QIing for correct interface");
+
+ if (FAILED(hr) || !pEventItf)
+ COMPlusThrowHR(CONNECT_E_CANNOTCONNECT);
+
+ COMOBJECTREF pEventItfObj = NULL;
+ OBJECTREF pTCEProviderObj = NULL;
+
+ GCPROTECT_BEGIN(pEventItfObj)
+ GCPROTECT_BEGIN(pTCEProviderObj)
+ {
+ // Create a COM+ object ref to wrap the event interface.
+ GetObjectRefFromComIP((OBJECTREF*)&pEventItfObj, pUnk, NULL);
+ IfNullThrow(pEventItfObj);
+
+ // Get the TCE provider COM+ object from the wrapper
+ pTCEProviderObj = m_pOwnerWrap->GetObjectRef();
+
+ for (int cEventMethod = 0; cEventMethod < m_NumEventMethods; cEventMethod++)
+ {
+ // If the managed object supports the event that call the AddEventX method.
+ if (m_apEventMethods[cEventMethod].m_pEventMethod)
+ InvokeProviderMethod( pTCEProviderObj, (OBJECTREF) pEventItfObj, m_apEventMethods[cEventMethod].m_pAddMethod, m_apEventMethods[cEventMethod].m_pEventMethod );
+ }
+
+ // Allocate the object handle and the connection cookie.
+ OBJECTHANDLEHolder phndEventItfObj = GetAppDomain()->CreateHandle((OBJECTREF)pEventItfObj);
+ ConnectionCookieHolder pConCookie = ConnectionCookie::CreateConnectionCookie(phndEventItfObj);
+
+ // pConCookie owns the handle now and will destroy it on exception
+ phndEventItfObj.SuppressRelease();
+
+ // Add the connection cookie to the list.
+ InsertWithLock(pConCookie);
+
+ // Everything went ok so hand back the cookie id.
+ *pdwCookie = pConCookie->m_id;
+
+ pConCookie.SuppressRelease();
+ }
+ GCPROTECT_END();
+ GCPROTECT_END();
+}
+
+void ConnectionPoint::UnadviseWorker(DWORD dwCookie)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ COMOBJECTREF pEventItfObj = NULL;
+ OBJECTREF pTCEProviderObj = NULL;
+
+ GCPROTECT_BEGIN(pEventItfObj)
+ GCPROTECT_BEGIN(pTCEProviderObj)
+ {
+ // The cookie is actually a connection cookie.
+ ConnectionCookieHolder pConCookie = FindWithLock(dwCookie);
+
+ // Retrieve the COM+ object from the cookie which in fact is the object handle.
+ pEventItfObj = (COMOBJECTREF) ObjectFromHandle(pConCookie->m_hndEventProvObj);
+ if (!pEventItfObj)
+ COMPlusThrowHR(E_INVALIDARG);
+
+ // Get the object from the wrapper
+ pTCEProviderObj = m_pOwnerWrap->GetObjectRef();
+
+ for (int cEventMethod = 0; cEventMethod < m_NumEventMethods; cEventMethod++)
+ {
+ // If the managed object supports the event that call the RemoveEventX method.
+ if (m_apEventMethods[cEventMethod].m_pEventMethod)
+ {
+ InvokeProviderMethod(pTCEProviderObj, (OBJECTREF) pEventItfObj, m_apEventMethods[cEventMethod].m_pRemoveMethod, m_apEventMethods[cEventMethod].m_pEventMethod);
+ }
+ }
+
+ // Remove the connection cookie from the list.
+ FindAndRemoveWithLock(pConCookie);
+ }
+ GCPROTECT_END();
+ GCPROTECT_END();
+}
+
+void ConnectionPoint::SetupEventMethods()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ INJECT_FAULT(COMPlusThrowOM());
+ }
+ CONTRACTL_END;
+
+ // Remember the number of not supported events.
+ int cNonSupportedEvents = 0;
+
+ // Retrieve the total number of event methods present on the source interface.
+ int cMaxNumEventMethods = m_pEventItfMT->GetNumMethods();
+
+ // If there are no methods then there is nothing to do.
+ if (cMaxNumEventMethods == 0)
+ return;
+
+ // Allocate the event method tables.
+ NewArrayHolder<EventMethodInfo> EventMethodInfos = new EventMethodInfo[cMaxNumEventMethods];
+
+ // Find all the real event methods needed to be able to advise on the current connection point.
+ int NumEventMethods = 0;
+ for (int cEventMethod = 0; cEventMethod < cMaxNumEventMethods; cEventMethod++)
+ {
+ // Retrieve the method descriptor for the current method on the event interface.
+ MethodDesc *pEventMethodDesc = m_pEventItfMT->GetMethodDescForSlot(cEventMethod);
+ if (!pEventMethodDesc)
+ continue;
+
+ // Store the event method on the source interface.
+ EventMethodInfos[NumEventMethods].m_pEventMethod = pEventMethodDesc;
+
+ // Retrieve and store the add and remove methods for the event.
+ EventMethodInfos[NumEventMethods].m_pAddMethod = FindProviderMethodDesc(pEventMethodDesc, EventAdd);
+ EventMethodInfos[NumEventMethods].m_pRemoveMethod = FindProviderMethodDesc(pEventMethodDesc, EventRemove);
+
+ // Make sure we have found both the add and the remove methods.
+ if (!EventMethodInfos[NumEventMethods].m_pAddMethod || !EventMethodInfos[NumEventMethods].m_pRemoveMethod)
+ {
+ cNonSupportedEvents++;
+ continue;
+ }
+
+ // Increment the real number of event methods on the source interface.
+ NumEventMethods++;
+ }
+
+ // If the interface has methods and the object does not support any then we
+ // fail the connection.
+ if ((NumEventMethods == 0) && (cNonSupportedEvents > 0))
+ COMPlusThrowHR(CONNECT_E_NOCONNECTION);
+
+ // Now that the struct is totally setup, we'll set the members.
+ m_NumEventMethods = NumEventMethods;
+ m_apEventMethods = EventMethodInfos;
+ EventMethodInfos.SuppressRelease();
+}
+
+MethodDesc *ConnectionPoint::FindProviderMethodDesc( MethodDesc *pEventMethodDesc, EnumEventMethods Method )
+{
+ CONTRACT (MethodDesc*)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(pEventMethodDesc));
+ PRECONDITION(Method == EventAdd || Method == EventRemove);
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END
+
+ // Retrieve the event method.
+ MethodDesc *pProvMethodDesc =
+ MemberLoader::FindEventMethod(m_pTCEProviderMT, pEventMethodDesc->GetName(), Method, MemberLoader::FM_IgnoreCase);
+ if (!pProvMethodDesc)
+ RETURN NULL;
+
+ // Validate that the signature of the delegate is the expected signature.
+ MetaSig Sig(pProvMethodDesc);
+ if (Sig.NextArg() != ELEMENT_TYPE_CLASS)
+ RETURN NULL;
+
+ // <TODO>@TODO: this ignores the type of failure - try GetLastTypeHandleThrowing()</TODO>
+ TypeHandle DelegateType = Sig.GetLastTypeHandleNT();
+ if (DelegateType.IsNull())
+ RETURN NULL;
+
+ PCCOR_SIGNATURE pEventMethSig;
+ DWORD cEventMethSig;
+ pEventMethodDesc->GetSig(&pEventMethSig, &cEventMethSig);
+ MethodDesc *pInvokeMD = MemberLoader::FindMethod(DelegateType.GetMethodTable(),
+ "Invoke",
+ pEventMethSig,
+ cEventMethSig,
+ pEventMethodDesc->GetModule());
+
+ if (!pInvokeMD)
+ RETURN NULL;
+
+ // The requested method exists and has the appropriate signature.
+ RETURN pProvMethodDesc;
+}
+
+void ConnectionPoint::InvokeProviderMethod( OBJECTREF pProvider, OBJECTREF pSubscriber, MethodDesc *pProvMethodDesc, MethodDesc *pEventMethodDesc )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ INJECT_FAULT(COMPlusThrowOM());
+ PRECONDITION(CheckPointer(pProvMethodDesc));
+ PRECONDITION(CheckPointer(pEventMethodDesc));
+ }
+ CONTRACTL_END;
+
+ GCPROTECT_BEGIN (pSubscriber);
+ GCPROTECT_BEGIN (pProvider);
+ {
+ // Create a method signature to extract the type of the delegate.
+ MetaSig MethodSig( pProvMethodDesc);
+ _ASSERTE( 1 == MethodSig.NumFixedArgs() );
+
+ // Go to the first argument.
+ CorElementType ArgType = MethodSig.NextArg();
+ _ASSERTE( ELEMENT_TYPE_CLASS == ArgType );
+
+ // Retrieve the EE class representing the argument.
+ MethodTable *pDelegateCls = MethodSig.GetLastTypeHandleThrowing().GetMethodTable();
+
+ // Make sure we activate the assembly containing the target method desc
+ pEventMethodDesc->EnsureActive();
+
+ // Allocate an object based on the method table of the delegate class.
+ OBJECTREF pDelegate = pDelegateCls->Allocate();
+
+ GCPROTECT_BEGIN( pDelegate );
+ {
+ // Initialize the delegate using the arguments structure.
+ // <TODO>Generics: ensure we get the right MethodDesc here and in similar places</TODO>
+ // Accept both void (object, native int) and void (object, native uint)
+ MethodDesc *pDlgCtorMD = MemberLoader::FindConstructor(pDelegateCls, &gsig_IM_Obj_IntPtr_RetVoid);
+ if (pDlgCtorMD == NULL)
+ pDlgCtorMD = MemberLoader::FindConstructor(pDelegateCls, &gsig_IM_Obj_UIntPtr_RetVoid);
+
+ // The loader is responsible for only accepting well-formed delegate classes.
+ _ASSERTE(pDlgCtorMD);
+
+ MethodDescCallSite dlgCtor(pDlgCtorMD);
+
+ ARG_SLOT CtorArgs[3] = { ObjToArgSlot(pDelegate),
+ ObjToArgSlot(pSubscriber),
+ (ARG_SLOT)pEventMethodDesc->GetMultiCallableAddrOfCode()
+ };
+ dlgCtor.Call(CtorArgs);
+
+ MethodDescCallSite prov(pProvMethodDesc, &pProvider);
+
+ // Do the actual invocation of the method method.
+ ARG_SLOT Args[2] = { ObjToArgSlot( pProvider ), ObjToArgSlot( pDelegate ) };
+ prov.Call(Args);
+ }
+ GCPROTECT_END();
+ }
+ GCPROTECT_END();
+ GCPROTECT_END();
+}
+
+void ConnectionPoint::InsertWithLock(ConnectionCookie* pConCookie)
+{
+ WRAPPER_NO_CONTRACT;
+
+ LockHolder lh(this);
+
+ bool fDone = false;
+
+ //
+ // handle special cases
+ //
+
+ if (NULL == m_pLastInserted)
+ {
+ //
+ // Special case 1: List is empty.
+ //
+ CONSISTENCY_CHECK(NULL == m_ConnectionList.GetHead());
+
+ pConCookie->m_id = 1;
+ m_ConnectionList.InsertHead(pConCookie);
+ fDone = true;
+ }
+
+ if (!fDone && ((NULL != m_pLastInserted->m_Link.m_pNext) || (idUpperLimit == m_pLastInserted->m_id)))
+ {
+ //
+ // Special case 2: Last inserted is somewhere in the middle of the list or we last
+ // inserted the max token id (we've wrapped around) and ID 1 is not
+ // taken.
+ //
+ CONSISTENCY_CHECK(NULL != m_ConnectionList.GetHead());
+
+ if (1 != m_ConnectionList.GetHead()->m_id)
+ {
+ // if ID 1 is not taken, we can just insert there.
+ pConCookie->m_id = 1;
+ m_ConnectionList.InsertHead(pConCookie);
+ fDone = true;
+ }
+ }
+
+ //
+ // General cases
+ //
+ if (!fDone)
+ {
+ ConnectionCookie* pLocationToStartSearchForInsertPoint = NULL;
+ ConnectionCookie* pInsertionPoint = NULL;
+
+ if (NULL == m_pLastInserted->m_Link.m_pNext)
+ {
+ if (idUpperLimit == m_pLastInserted->m_id)
+ {
+ CONSISTENCY_CHECK(1 == m_ConnectionList.GetHead()->m_id); // should be handled by special case #2
+
+ // we need to wrap around
+ // scan from head for first hole, insert there
+ pLocationToStartSearchForInsertPoint = m_ConnectionList.GetHead();
+ }
+ else
+ {
+ // Most common case: insert at tail, incrementing ID
+ pInsertionPoint = m_pLastInserted;
+ pLocationToStartSearchForInsertPoint = NULL; // don't do any searching, just append
+ }
+ }
+ else
+ {
+ // scan from m_pLastInserted for first hole, insert there
+ pLocationToStartSearchForInsertPoint = m_pLastInserted;
+ }
+
+ if (NULL != pLocationToStartSearchForInsertPoint)
+ {
+ // Starting from pLocationToStartSearchForInsertPoint, scan list to find a
+ // discontinuity in the IDs. Insert there.
+
+ ConnectionCookie* pCurrentNode = pLocationToStartSearchForInsertPoint;
+
+ //
+ // limit case is where we've wrapped around the whole list and found the initial start point again.
+ //
+ while (true)
+ {
+ if (NULL == pCurrentNode->m_Link.m_pNext)
+ {
+ if (pCurrentNode->m_id < idUpperLimit)
+ {
+ // if we reach the end of the list and we have free IDs, let's use them.
+ break;
+ }
+ pCurrentNode = m_ConnectionList.GetHead();
+ }
+ else
+ {
+ ConnectionCookie* pNext = CONTAINING_RECORD(pCurrentNode->m_Link.m_pNext, ConnectionCookie, m_Link);
+ if ((pCurrentNode->m_id + 1) < pNext->m_id)
+ {
+ break;
+ }
+ pCurrentNode = pNext;
+ }
+
+ if (pCurrentNode == pLocationToStartSearchForInsertPoint)
+ {
+ // we came back to the node where we started which means that there's no gap and
+ // all IDs up to idUpperLimit are taken
+ EX_THROW(HRException, (CONNECT_E_ADVISELIMIT));
+ }
+ }
+
+ pInsertionPoint = pCurrentNode;
+ }
+
+
+ CONSISTENCY_CHECK(NULL != pInsertionPoint);
+ CONSISTENCY_CHECK(idUpperLimit != pInsertionPoint->m_id);
+
+#ifdef _DEBUG
+ ConnectionCookie* pNextCookieNode = CONTAINING_RECORD(pInsertionPoint->m_Link.m_pNext, ConnectionCookie, m_Link);
+ DWORD idNew = pInsertionPoint->m_id + 1;
+ CONSISTENCY_CHECK(NULL == pNextCookieNode ||
+ ((pInsertionPoint->m_id < idNew) &&
+ (idNew < pNextCookieNode->m_id)));
+#endif // _DEBUG
+
+ pConCookie->m_id = pInsertionPoint->m_id + 1;
+ pInsertionPoint->m_Link.InsertAfter(&pConCookie->m_Link);
+ }
+
+ m_pLastInserted = pConCookie;
+}
+
+ConnectionCookie* ConnectionPoint::FindWithLock(DWORD idOfCookie)
+{
+ WRAPPER_NO_CONTRACT;
+
+ ConnectionCookie* pCurrentNode;
+
+ {
+ LockHolder lh(this);
+
+ pCurrentNode = m_ConnectionList.GetHead();
+
+ while (pCurrentNode && (pCurrentNode->m_id != idOfCookie))
+ {
+ pCurrentNode = CONTAINING_RECORD(pCurrentNode->m_Link.m_pNext, ConnectionCookie, m_Link);
+ }
+ }
+
+ if (NULL == pCurrentNode)
+ {
+ EX_THROW(HRException, (CONNECT_E_NOCONNECTION));
+ }
+
+ return pCurrentNode;
+}
+
+
+void ConnectionPoint::FindAndRemoveWithLock(ConnectionCookie* pConCookie)
+{
+ WRAPPER_NO_CONTRACT;
+
+ LockHolder lh(this);
+
+ m_ConnectionList.FindAndRemove(pConCookie);
+
+ if (pConCookie == m_pLastInserted)
+ {
+ m_pLastInserted = m_ConnectionList.GetHead();
+ }
+}
+
+ConnectionPointEnum::ConnectionPointEnum(ComCallWrapper *pOwnerWrap, CQuickArray<ConnectionPoint*> *pCPList)
+: m_pOwnerWrap(pOwnerWrap)
+, m_pCPList(pCPList)
+, m_CurrPos(0)
+, m_cbRefCount(0)
+, m_Lock(CrstInterop)
+{
+ WRAPPER_NO_CONTRACT;
+
+ m_pOwnerWrap->AddRef();
+}
+
+ConnectionPointEnum::~ConnectionPointEnum()
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (m_pOwnerWrap)
+ m_pOwnerWrap->Release();
+}
+
+HRESULT __stdcall ConnectionPointEnum::QueryInterface(REFIID riid, void** ppv)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppv, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!ppv)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppv = NULL;
+
+ SetupForComCallHR();
+
+ if (riid == IID_IEnumConnectionPoints)
+ {
+ *ppv = static_cast<IEnumConnectionPoints*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppv = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+
+ ULONG cbRef = SafeAddRefPreemp((IUnknown*)*ppv);
+ //@TODO(CLE) AddRef logging that doesn't use QI
+
+ return S_OK;
+}
+
+ULONG __stdcall ConnectionPointEnum::AddRef()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ LONG i = FastInterlockIncrement((LONG*)&m_cbRefCount );
+ return i;
+}
+
+ULONG __stdcall ConnectionPointEnum::Release()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ HRESULT hr = S_OK;
+ ULONG cbRef = -1;
+
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ cbRef = FastInterlockDecrement((LONG*)&m_cbRefCount );
+ _ASSERTE(cbRef >=0);
+ if (cbRef == 0)
+ delete this;
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return cbRef;
+}
+
+HRESULT __stdcall ConnectionPointEnum::Next(ULONG cConnections, IConnectionPoint **ppCP, ULONG *pcFetched)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppCP, NULL_OK));
+ PRECONDITION(CheckPointer(pcFetched, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (NULL == ppCP)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ if (pcFetched)
+ *pcFetched = 0;
+
+ SetupForComCallHR();
+
+ UINT cFetched;
+
+ // Acquire the lock before we start traversing the connection point list.
+ {
+ LockHolder lh(this);
+
+ for (cFetched = 0; cFetched < cConnections && m_CurrPos < m_pCPList->Size(); cFetched++, m_CurrPos++)
+ {
+ ppCP[cFetched] = (*m_pCPList)[m_CurrPos];
+ SafeAddRefPreemp(ppCP[cFetched]);
+ }
+
+ if (pcFetched)
+ *pcFetched = cFetched;
+ }
+
+ return cFetched == cConnections ? S_OK : S_FALSE;
+}
+
+HRESULT __stdcall ConnectionPointEnum::Skip(ULONG cConnections)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ // Acquire the lock before we start traversing the connection point list.
+ {
+ LockHolder lh(this);
+
+ if(m_CurrPos + cConnections <= m_pCPList->Size())
+ {
+ // There are enough connection points left in the list to allow
+ // us to skip the required number.
+ m_CurrPos += cConnections;
+ return S_OK;
+ }
+ else
+ {
+ // There aren't enough connection points left so set the current
+ // position to the end of the list and return S_FALSE to indicate
+ // we couldn't skip the requested number.
+ m_CurrPos = (UINT)m_pCPList->Size();
+ return S_FALSE;
+ }
+ }
+}
+
+HRESULT __stdcall ConnectionPointEnum::Reset()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ // Acquire the lock before we start traversing the connection point list.
+ {
+ LockHolder lh(this);
+ m_CurrPos = 0;
+ }
+
+ return S_OK;
+}
+
+HRESULT __stdcall ConnectionPointEnum::Clone(IEnumConnectionPoints **ppEnum)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppEnum, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!ppEnum)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppEnum = NULL;
+
+ SetupForComCallHR();
+
+ ConnectionPointEnum *pCPEnum;
+ {
+ CONTRACT_VIOLATION(ThrowsViolation); //ConnectionPointEnum throws
+ pCPEnum = new(nothrow) ConnectionPointEnum(m_pOwnerWrap, m_pCPList);
+ }
+ if (!pCPEnum)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = SafeQueryInterfacePreemp(pCPEnum, IID_IEnumConnectionPoints, (IUnknown**)ppEnum);
+ LogInteropQI(pCPEnum, IID_IEnumConnectionPoints, hr, "ConnectionPointEnum::Clone: QIing for IID_IEnumConnectionPoints");
+
+ return hr;
+}
+
+ConnectionEnum::ConnectionEnum(ConnectionPoint *pConnectionPoint)
+: m_pConnectionPoint(pConnectionPoint)
+, m_CurrCookie(pConnectionPoint->GetCookieList()->GetHead())
+, m_cbRefCount(0)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ ULONG cbRef = SafeAddRefPreemp(m_pConnectionPoint);
+ //@TODO(CLE) AddRef logging that doesn't use QI
+}
+
+ConnectionEnum::~ConnectionEnum()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ ULONG cbRef = SafeReleasePreemp(m_pConnectionPoint);
+ LogInteropRelease(m_pConnectionPoint, cbRef, "ConnectionEnum::~ConnectionEnum: Releasing the connection point object");
+}
+
+HRESULT __stdcall ConnectionEnum::QueryInterface(REFIID riid, void** ppv)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppv, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!ppv)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppv = NULL;
+
+ SetupForComCallHR();
+
+ if (riid == IID_IEnumConnections)
+ {
+ *ppv = static_cast<IEnumConnections*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppv = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+
+ ULONG cbRef = SafeAddRefPreemp((IUnknown*)*ppv);
+ //@TODO(CLE) AddRef logging that doesn't use QI
+
+ return S_OK;
+}
+
+ULONG __stdcall ConnectionEnum::AddRef()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ SO_TOLERANT;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ LONG i = FastInterlockIncrement((LONG*)&m_cbRefCount);
+ return i;
+}
+
+ULONG __stdcall ConnectionEnum::Release()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ LONG i = FastInterlockDecrement((LONG*)&m_cbRefCount);
+ _ASSERTE(i >=0);
+ if (i == 0)
+ delete this;
+
+ return i;
+}
+
+HRESULT __stdcall ConnectionEnum::Next(ULONG cConnections, CONNECTDATA* rgcd, ULONG *pcFetched)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(rgcd, NULL_OK));
+ PRECONDITION(CheckPointer(pcFetched, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (NULL == rgcd)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ if (pcFetched)
+ *pcFetched = 0;
+
+ SetupForComCallHR();
+
+ HRESULT hr = S_OK;
+ UINT cFetched;
+ CONNECTIONCOOKIELIST *pConnectionList = m_pConnectionPoint->GetCookieList();
+
+ // Acquire the connection point's lock before we start traversing the connection list.
+ {
+ ConnectionPoint::LockHolder lh(m_pConnectionPoint);
+
+ {
+ // Switch to cooperative GC mode before we manipulate OBJCETREF's.
+ GCX_COOP();
+
+ for (cFetched = 0; cFetched < cConnections && m_CurrCookie; cFetched++)
+ {
+ {
+ CONTRACT_VIOLATION(ThrowsViolation);
+ rgcd[cFetched].pUnk = GetComIPFromObjectRef((OBJECTREF*)m_CurrCookie->m_hndEventProvObj, ComIpType_Unknown, NULL);
+ rgcd[cFetched].dwCookie = m_CurrCookie->m_id;
+ }
+ m_CurrCookie = pConnectionList->GetNext(m_CurrCookie);
+ }
+ }
+
+ // Leave the lock now that we are done traversing the list.
+ }
+
+ // Set the count of fetched connections if the caller desires it.
+ if (pcFetched)
+ *pcFetched = cFetched;
+
+ return cFetched == cConnections ? S_OK : S_FALSE;
+}
+
+HRESULT __stdcall ConnectionEnum::Skip(ULONG cConnections)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ HRESULT hr = S_FALSE;
+ CONNECTIONCOOKIELIST *pConnectionList = m_pConnectionPoint->GetCookieList();
+
+ {
+ ConnectionPoint::LockHolder lh(m_pConnectionPoint);
+
+ // Try and skip the requested number of connections.
+ while (m_CurrCookie && cConnections)
+ {
+ m_CurrCookie = pConnectionList->GetNext(m_CurrCookie);
+ cConnections--;
+ }
+ // Leave the lock now that we are done traversing the list.
+ }
+
+ // Check to see if we succeeded.
+ return cConnections == 0 ? S_OK : S_FALSE;
+}
+
+HRESULT __stdcall ConnectionEnum::Reset()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ SetupForComCallHR();
+
+ // Set the current cookie back to the head of the list. We must acquire the
+ // connection point lock before we touch the list.
+ ConnectionPoint::LockHolder lh(m_pConnectionPoint);
+
+ m_CurrCookie = m_pConnectionPoint->GetCookieList()->GetHead();
+
+ return S_OK;
+}
+
+HRESULT __stdcall ConnectionEnum::Clone(IEnumConnections **ppEnum)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(ppEnum, NULL_OK));
+ }
+ CONTRACTL_END;
+
+ // Verify the arguments.
+ if (!ppEnum)
+ return E_POINTER;
+
+ // Initialize the out parameters.
+ *ppEnum = NULL;
+
+ // This should setup a SO_INTOLERANT region, why isn't it?
+ SetupForComCallHR();
+
+ ConnectionEnum *pConEnum = new(nothrow) ConnectionEnum(m_pConnectionPoint);
+ if (!pConEnum)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = SafeQueryInterfacePreemp(pConEnum, IID_IEnumConnections, (IUnknown**)ppEnum);
+ LogInteropQI(pConEnum, IID_IEnumConnections, hr, "ConnectionEnum::Clone: QIing for IID_IEnumConnections");
+
+ return hr;
+}