summaryrefslogtreecommitdiff
path: root/src/vm/comcache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/comcache.cpp')
-rw-r--r--src/vm/comcache.cpp1626
1 files changed, 1626 insertions, 0 deletions
diff --git a/src/vm/comcache.cpp b/src/vm/comcache.cpp
new file mode 100644
index 0000000000..a1f2416c5c
--- /dev/null
+++ b/src/vm/comcache.cpp
@@ -0,0 +1,1626 @@
+// 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.
+
+
+
+#include "common.h"
+
+#include <crtwrap.h>
+#include "comcache.h"
+#include "runtimecallablewrapper.h"
+#include "mtx.h"
+#include "contxt.h"
+#include "ctxtcall.h"
+#include "win32threadpool.h"
+#include "mdaassistants.h"
+
+#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT
+#include "olecontexthelpers.h"
+#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT
+
+
+//================================================================
+// Guid definitions.
+const GUID IID_IEnterActivityWithNoLock = { 0xd7174f82, 0x36b8, 0x4aa8, { 0x80, 0x0a, 0xe9, 0x63, 0xab, 0x2d, 0xfa, 0xb9 } };
+const GUID IID_IFuncEvalAbort = { 0xde6844f6, 0x95ac, 0x4e83, { 0x90, 0x8d, 0x9b, 0x1b, 0xea, 0x2f, 0xe0, 0x8c } };
+
+// sanity check., to find stress bug #82137
+VOID IUnkEntry::CheckValidIUnkEntry()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (IsDisconnected())
+ {
+ COMPlusThrow(kInvalidComObjectException, IDS_EE_COM_OBJECT_NO_LONGER_HAS_WRAPPER);
+ }
+}
+
+// Version that returns an HR instead of throwing.
+HRESULT IUnkEntry::HRCheckValidIUnkEntry()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (IsDisconnected())
+ {
+ return COR_E_INVALIDCOMOBJECT;
+ }
+
+ return S_OK;
+}
+
+// Returns IErrorInfo corresponding to the exception injected by the debugger to abort a func eval,
+// or NULL if there is no such exception.
+static IErrorInfo *CheckForFuncEvalAbortNoThrow(HRESULT hr)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // the managed exception thrown by the debugger is translated to EXCEPTION_COMPLUS by COM
+ if (hr == EXCEPTION_COMPLUS)
+ {
+ GCX_PREEMP();
+
+ // we recognize the ones thrown by the debugger by QI'ing the IErrorInfo for a special IID
+ ReleaseHolder<IErrorInfo> pErrorInfo;
+ if (SafeGetErrorInfo(&pErrorInfo) == S_OK)
+ {
+ ReleaseHolder<IUnknown> pUnk;
+ if (SafeQueryInterface(pErrorInfo, IID_IFuncEvalAbort, &pUnk) == S_OK)
+ {
+ // QI succeeded, this is a func eval abort
+ return pErrorInfo.Extract();
+ }
+ else
+ {
+ // QI failed, put the IErrorInfo back
+ LeaveRuntimeHolderNoThrow lrh((size_t)SetErrorInfo);
+ if (SUCCEEDED(lrh.GetHR()))
+ {
+ SetErrorInfo(0, pErrorInfo);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// Rethrows the exception injected by the debugger to abort a func eval, or does nothing if there is no such exception.
+static void CheckForFuncEvalAbort(HRESULT hr)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ IErrorInfo *pErrorInfo = CheckForFuncEvalAbortNoThrow(hr);
+ if (pErrorInfo != NULL)
+ {
+ // COMPlusThrowHR internally releases the pErrorInfo
+ COMPlusThrowHR(hr, pErrorInfo);
+ }
+}
+
+//+-------------------------------------------------------------------------
+//
+// Function: STDAPI_(LPSTREAM) CreateMemStm(DWORD cb, BYTE** ppBuf))
+// Create a stream in the memory
+//
+STDAPI_(LPSTREAM) CreateMemStm(DWORD cb, BYTE** ppBuf)
+{
+ CONTRACT(LPSTREAM)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ INJECT_FAULT(CONTRACT_RETURN NULL);
+ PRECONDITION(CheckPointer(ppBuf, NULL_OK));
+ PRECONDITION(CheckPointer(ppBuf, NULL_OK));
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END;
+
+ LPSTREAM pstm = NULL;
+
+ BYTE* pMem = new(nothrow) BYTE[cb];
+ if (pMem)
+ {
+ HRESULT hr = CInMemoryStream::CreateStreamOnMemory(pMem, cb, &pstm, TRUE);
+ _ASSERTE(hr == S_OK || pstm == NULL);
+ }
+
+ if(ppBuf)
+ *ppBuf = pMem;
+
+ RETURN pstm;
+}
+
+//=====================================================================
+// HRESULT wCoMarshalInterThreadInterfaceInStream
+HRESULT wCoMarshalInterThreadInterfaceInStream(REFIID riid,
+ LPUNKNOWN pUnk,
+ LPSTREAM *ppStm)
+{
+#ifdef PLATFORM_CE
+ return E_NOTIMPL;
+#endif // !PLATFORM_CE
+
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(CheckPointer(pUnk));
+ PRECONDITION(CheckPointer(ppStm));
+ }
+ CONTRACTL_END;
+
+ HRESULT hr;
+ LPSTREAM pStm = NULL;
+
+ DWORD mshlFlgs = MSHLFLAGS_NORMAL;
+
+ ULONG lSize;
+ hr = CoGetMarshalSizeMax(&lSize, IID_IUnknown, pUnk, MSHCTX_INPROC, NULL, mshlFlgs);
+
+ if (hr == S_OK)
+ {
+ // Create a stream
+ pStm = CreateMemStm(lSize, NULL);
+
+ if (pStm != NULL)
+ {
+ // Marshal the interface into the stream TABLE STRONG
+ hr = CoMarshalInterface(pStm, riid, pUnk, MSHCTX_INPROC, NULL, mshlFlgs);
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Reset the stream to the begining
+ LARGE_INTEGER li;
+ LISet32(li, 0);
+ ULARGE_INTEGER li2;
+ pStm->Seek(li, STREAM_SEEK_SET, &li2);
+
+ // Set the return value
+ *ppStm = pStm;
+ }
+ else
+ {
+ // Cleanup if failure
+ SafeReleasePreemp(pStm);
+ *ppStm = NULL;
+ }
+
+ // Return the result
+ return hr;
+}
+
+//================================================================
+// Struct passed in to DoCallback.
+struct CtxEntryEnterContextCallbackData
+{
+ PFNCTXCALLBACK m_pUserCallbackFunc;
+ LPVOID m_pUserData;
+ LPVOID m_pCtxCookie;
+ HRESULT m_UserCallbackHR;
+#ifdef MDA_SUPPORTED
+ CLREvent* m_hTimeoutEvent;
+#endif
+};
+
+//================================================================
+// Static members.
+CtxEntryCache* CtxEntryCache::s_pCtxEntryCache = NULL;
+
+CtxEntryCache::CtxEntryCache()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ m_Lock.Init(LOCK_COMCTXENTRYCACHE);
+ LockOwner lock = {&m_Lock, IsOwnerOfSpinLock};
+}
+
+CtxEntryCache::~CtxEntryCache()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ for (SHash<CtxEntryCacheTraits>::Iterator it = m_CtxEntryHash.Begin(); it != m_CtxEntryHash.End(); it++)
+ {
+ CtxEntry *pCtxEntry = (CtxEntry *)*it;
+ _ASSERTE(pCtxEntry);
+ LPVOID CtxCookie = pCtxEntry->GetCtxCookie();
+ m_CtxEntryHash.Remove(CtxCookie);
+
+ LOG((LF_INTEROP, LL_INFO100, "Leaked CtxEntry %8.8x with CtxCookie %8.8x, ref count %d\n", pCtxEntry, pCtxEntry->GetCtxCookie(), pCtxEntry->m_dwRefCount));
+ pCtxEntry->m_dwRefCount = 0;
+ delete pCtxEntry;
+ }
+}
+
+
+void CtxEntryCache::Init()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+
+ // This should never be called more than once.
+ PRECONDITION(NULL == s_pCtxEntryCache);
+ }
+ CONTRACTL_END;
+
+ // Allocate the one and only instance of the context entry cache.
+ s_pCtxEntryCache = new CtxEntryCache();
+}
+
+CtxEntryCache* CtxEntryCache::GetCtxEntryCache()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(s_pCtxEntryCache));
+ }
+ CONTRACTL_END;
+
+ return s_pCtxEntryCache;
+}
+
+CtxEntry* CtxEntryCache::CreateCtxEntry(LPVOID pCtxCookie, Thread * pSTAThread)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ CtxEntry * pCtxEntry = NULL;
+ Thread * pThread = GetThread();
+
+ // If we don't already have a context entry for the context cookie,
+ // we need to create one.
+ NewHolder<CtxEntry> pNewCtxEntry = new CtxEntry(pCtxCookie, pSTAThread);
+ // tiggers GC, can't happen when we hold spin lock
+ pNewCtxEntry->Init();
+
+ {
+ TAKE_SPINLOCK_AND_DONOT_TRIGGER_GC(&m_Lock);
+
+ // double check for race
+ pCtxEntry = m_CtxEntryHash.Lookup(pCtxCookie);
+ if (pCtxEntry == NULL)
+ {
+ // We successfully allocated and initialized the entry.
+ m_CtxEntryHash.Add(pNewCtxEntry);
+ pCtxEntry = pNewCtxEntry.Extract();
+ }
+ // We must have an entry now; we need to addref it before
+ // we leave the lock.
+ pCtxEntry->AddRef ();
+ }
+
+ return pCtxEntry;
+}
+
+CtxEntry* CtxEntryCache::FindCtxEntry(LPVOID pCtxCookie, Thread *pThread)
+{
+ CtxEntry *pCtxEntry = NULL;
+ Thread *pSTAThread = NULL;
+
+ CONTRACT (CtxEntry*)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ INJECT_FAULT(COMPlusThrowOM());
+ PRECONDITION(CheckPointer(pCtxCookie));
+ POSTCONDITION(CheckPointer(RETVAL));
+ POSTCONDITION(pCtxCookie == pCtxEntry->GetCtxCookie());
+ POSTCONDITION(pSTAThread == pCtxEntry->GetSTAThread());
+ }
+ CONTRACT_END;
+
+ // Find our STA (if any)
+ if (pThread->GetApartment() == Thread::AS_InSTA)
+ {
+ // We are in an STA thread. But we may be in a NA context, so do an extra
+ // check for that case.
+ BOOL fNAContext;
+
+ // try the simple cache on Thread first
+ if (pCtxCookie != pThread->GetLastSTACtxCookie(&fNAContext))
+ {
+ APTTYPE type;
+ fNAContext = (SUCCEEDED(GetCurrentApartmentTypeNT5((IObjectContext *)pCtxCookie, &type)) && type == APTTYPE_NA);
+ pThread->SetLastSTACtxCookie(pCtxCookie, fNAContext);
+ }
+
+ if (!fNAContext)
+ pSTAThread = pThread;
+ }
+
+ ASSERT (GetThread ());
+ BOOL bFound = FALSE;
+
+ ACQUIRE_SPINLOCK_NO_HOLDER(&m_Lock, pThread);
+ {
+ // Try to find a context entry for the context cookie.
+ pCtxEntry = m_CtxEntryHash.Lookup(pCtxCookie);
+ if (pCtxEntry)
+ {
+ // We must have an entry now; we need to addref it before
+ // we leave the lock.
+ pCtxEntry->AddRef ();
+ bFound = TRUE;
+ }
+ }
+ RELEASE_SPINLOCK_NO_HOLDER(&m_Lock, pThread);
+
+ if (!bFound)
+ {
+ pCtxEntry = CreateCtxEntry(pCtxCookie, pSTAThread);
+ }
+
+ // Returned the found or allocated entry.
+ RETURN pCtxEntry;
+}
+
+
+void CtxEntryCache::TryDeleteCtxEntry(LPVOID pCtxCookie)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(pCtxCookie));
+ }
+ CONTRACTL_END;
+
+ BOOL bDelete = FALSE;
+ CtxEntry *pCtxEntry = NULL;
+
+ {
+ TAKE_SPINLOCK_AND_DONOT_TRIGGER_GC(&m_Lock);
+
+ // Try to find a context entry for the context cookie.
+ pCtxEntry = m_CtxEntryHash.Lookup(pCtxCookie);
+ if (pCtxEntry)
+ {
+ // If the ref count of the context entry is still 0, then we can
+ // remove the ctx entry and delete it.
+ if (pCtxEntry->m_dwRefCount == 0)
+ {
+ // First remove the context entry from the list.
+ m_CtxEntryHash.Remove(pCtxCookie);
+
+ // We need to unlock the context entry cache before we delete the
+ // context entry since this can cause release to be called on
+ // an IP which can cause us to re-enter the runtime thus causing a
+ // deadlock.
+ // We can now safely delete the context entry.
+ bDelete = TRUE;
+ }
+ }
+ }
+
+ if (bDelete)
+ {
+ delete pCtxEntry;
+ }
+}
+
+//================================================================
+// Get the RCW associated with this IUnkEntry
+// We assert inside Init that this IUnkEntry is indeed within a RCW
+RCW *IUnkEntry::GetRCW()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (RCW *) (((LPBYTE) this) - offsetof(RCW, m_UnkEntry));
+}
+
+//================================================================
+// Initialize the entry
+void IUnkEntry::Init(
+ IUnknown *pUnk,
+ BOOL bIsFreeThreaded,
+ Thread *pThread
+ DEBUGARG(RCW *pRCW)
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(CheckPointer(pUnk));
+ INDEBUG(PRECONDITION(CheckPointer(pRCW));)
+ }
+ CONTRACTL_END;
+
+ // Make sure this IUnkEntry is part of a RCW so that we can get back to the RCW through offset
+ // if we have to
+ _ASSERTE(((LPBYTE)pRCW) + offsetof(RCW, m_UnkEntry) == (LPBYTE) this);
+
+ // Find our context cookie
+ LPVOID pCtxCookie = GetCurrentCtxCookie();
+ _ASSERTE(pCtxCookie);
+
+ // Set up IUnkEntry's state.
+ if (bIsFreeThreaded)
+ m_pCtxEntry = NULL;
+ else
+ m_pCtxEntry = CtxEntryCache::GetCtxEntryCache()->FindCtxEntry(pCtxCookie, pThread);
+
+ m_pUnknown = pUnk;
+ m_pCtxCookie = pCtxCookie;
+ m_pStream = NULL;
+
+ // Sanity check this IUnkEntry.
+ CheckValidIUnkEntry();
+}
+
+//================================================================
+// Release the interface pointer held by the IUnkEntry.
+VOID IUnkEntry::ReleaseInterface(RCW *pRCW)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ if (g_fProcessDetach)
+ {
+ // The Release call is unsafe if the process is going away (calls into
+ // DLLs we don't know are even mapped).
+ LogInteropLeak(this);
+ }
+ else
+ {
+ // now release the IUnknown that we hold
+ if ((m_pUnknown != 0) && (m_pUnknown != (IUnknown *)0xBADF00D))
+ {
+ ULONG cbRef = SafeReleasePreemp(m_pUnknown, pRCW);
+ LogInteropRelease(m_pUnknown, cbRef, "IUnkEntry::Free: Releasing the held ref");
+ }
+
+ // mark the entry as dead
+ m_pUnknown = (IUnknown *)0xBADF00D;
+ }
+}
+
+//================================================================
+// Free the IUnknown entry. ReleaseInterface must have been called.
+VOID IUnkEntry::Free()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(g_fProcessDetach || m_pUnknown == (IUnknown *)0xBADF00D);
+ }
+ CONTRACTL_END;
+
+ // Log the de-allocation of the IUnknown entry.
+ LOG((LF_INTEROP, LL_INFO10000, "IUnkEntry::Free called for context 0x%08X, to release entry with m_pUnknown %p, on thread %p\n", m_pCtxCookie, m_pUnknown, GetThread()));
+
+ if (g_fProcessDetach)
+ {
+ IStream *pOldStream = m_pStream;
+ if (InterlockedExchangeT(&m_pStream, NULL) == pOldStream)
+ SafeReleasePreemp(pOldStream);
+ }
+ else
+ {
+ IStream *pStream = m_pStream;
+ m_pStream = NULL;
+
+ // This will release the stream, object in the stream and the memory on which the stream was created
+ if (pStream)
+ SafeReleaseStream(pStream);
+
+ }
+
+ // Release the ref count we have on the CtxEntry.
+ CtxEntry *pEntry = GetCtxEntry();
+ if (pEntry)
+ {
+ pEntry->Release();
+ m_pCtxEntry = NULL;
+ }
+}
+
+//================================================================
+// Get IUnknown for the current context from IUnkEntry
+IUnknown* IUnkEntry::GetIUnknownForCurrContext(bool fNoAddRef)
+{
+ CONTRACT (IUnknown*)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ POSTCONDITION(CheckPointer(RETVAL, (fNoAddRef ? NULL_OK : NULL_NOT_OK)));
+ }
+ CONTRACT_END;
+
+ IUnknown* pUnk = NULL;
+ LPVOID pCtxCookie = GetCurrentCtxCookie();
+ _ASSERTE(pCtxCookie);
+
+ CheckValidIUnkEntry();
+
+ if (m_pCtxCookie == pCtxCookie || IsFreeThreaded())
+ {
+ pUnk = GetRawIUnknown_NoAddRef();
+
+ if (!fNoAddRef)
+ {
+ RCW_VTABLEPTR(GetRCW());
+ ULONG cbRef = SafeAddRef(pUnk);
+ LogInteropAddRef(pUnk, cbRef, "IUnkEntry::GetIUnknownForCurrContext: Addref pUnk, passing ref to caller");
+ }
+ }
+
+ if (pUnk == NULL && !fNoAddRef)
+ pUnk = UnmarshalIUnknownForCurrContext();
+
+ RETURN pUnk;
+}
+
+//================================================================
+// Unmarshal IUnknown for the current context from IUnkEntry
+IUnknown* IUnkEntry::UnmarshalIUnknownForCurrContext()
+{
+ CONTRACT (IUnknown*)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(!IsFreeThreaded());
+ POSTCONDITION(CheckPointer(RETVAL));
+ }
+ CONTRACT_END;
+
+ HRESULT hrCDH = S_OK;
+ IUnknown* pUnk = NULL;
+ BOOL fRetry = TRUE;
+ BOOL fUnmarshalFailed = FALSE;
+ BOOL fCallHelper = FALSE;
+
+ CheckValidIUnkEntry();
+
+ _ASSERTE(GetCtxEntry() != NULL);
+
+
+ if(IsMarshalingInhibited() && (m_pCtxCookie != GetCurrentCtxCookie()))
+ {
+ // We want to use an interface in a different context but it can't be marshalled.
+ LOG((LF_INTEROP, LL_INFO100, "IUnkEntry::GetIUnknownForCurrContext failed as the COM object has inhibited marshaling"));
+ COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST_NOMARSHAL);
+ }
+
+ // Make sure we are in preemptive GC mode before we call out to COM.
+ GCX_PREEMP();
+
+ // Need to synchronize
+ while (fRetry)
+ {
+ // Marshal the interface to the stream if it hasn't been done yet.
+ if (m_pStream == NULL)
+ {
+ // If context transition failed, we'll return a failure HRESULT
+ // Otherwise, we return S_OK but m_pStream will stay being NULL
+ hrCDH = MarshalIUnknownToStreamCallback(this);
+ CheckForFuncEvalAbort(hrCDH);
+
+#ifdef MDA_SUPPORTED
+ if (FAILED(hrCDH))
+ {
+ MDA_TRIGGER_ASSISTANT(DisconnectedContext, ReportViolationDisconnected(m_pCtxCookie, hrCDH));
+ }
+ else if (m_pStream == NULL)
+ {
+ MDA_TRIGGER_ASSISTANT(NotMarshalable, ReportViolation());
+ }
+#endif
+ }
+
+ if (TryUpdateEntry())
+ {
+ // If the interface is not marshalable or if we failed to
+ // enter the context, then we don't have any choice but to
+ // use the raw IP.
+ if (m_pStream == NULL)
+ {
+ // We retrieved an IP so stop retrying.
+ fRetry = FALSE;
+
+ // Give out this IUnknown we are holding
+ pUnk = GetRawIUnknown_NoAddRef();
+
+ RCW_VTABLEPTR(GetRCW());
+ ULONG cbRef = SafeAddRefPreemp(pUnk);
+
+ LogInteropAddRef(pUnk, cbRef, "UnmarshalIUnknownForCurrContext handing out raw IUnknown");
+ }
+ else
+ {
+ // we got control for this entry
+ // GetInterface for the current context
+ HRESULT hr;
+ hr = CoUnmarshalInterface(m_pStream, IID_IUnknown, (void **)&pUnk);
+
+ // If the objref in the stream times out, we need to go an marshal into the
+ // stream once again.
+ if (FAILED(hr))
+ {
+ _ASSERTE(m_pStream);
+
+ CheckForFuncEvalAbort(hr);
+
+ // This should release the stream, object in the stream and the memory on which the stream was created
+ SafeReleaseStream(m_pStream);
+ m_pStream = NULL;
+
+ // If unmarshal failed twice, then bail out.
+ if (fUnmarshalFailed)
+ {
+ fRetry = FALSE;
+
+ // Handing out m_pUnknown in this case would be incorrect. We should fix other places that are doing the same thing in Dev10
+ // To minimize code changes, throwing E_NOINTERFACE instead
+ COMPlusThrowHR(E_NOINTERFACE);
+ }
+
+ // Remember we failed to unmarshal.
+ fUnmarshalFailed = TRUE;
+ }
+ else
+ {
+ // Reset the stream to the begining
+ LARGE_INTEGER li;
+ LISet32(li, 0);
+ ULARGE_INTEGER li2;
+ m_pStream->Seek(li, STREAM_SEEK_SET, &li2);
+
+ // Marshal the interface into the stream with appropriate flags
+ hr = CoMarshalInterface(m_pStream,
+ IID_IUnknown, pUnk, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL);
+
+ if (FAILED(hr))
+ {
+ CheckForFuncEvalAbort(hr);
+
+ // The proxy is no longer valid. This sometimes manifests itself by
+ // a failure during re-marshaling it to the stream. When this happens,
+ // we need to release the the pUnk we extracted and the stream and try to
+ // re-create the stream. We don't want to release the stream data since
+ // we already extracted the proxy from the stream and released it.
+ RCW_VTABLEPTR(GetRCW());
+ SafeReleasePreemp(pUnk);
+
+ SafeReleasePreemp(m_pStream);
+ m_pStream = NULL;
+ }
+ else
+ {
+ // Reset the stream to the begining
+ LISet32(li, 0);
+ m_pStream->Seek(li, STREAM_SEEK_SET, &li2);
+
+ // We managed to unmarshal the IP from the stream, stop retrying.
+ fRetry = FALSE;
+ }
+ }
+ }
+
+ // Done with the entry.
+ EndUpdateEntry();
+ }
+ else
+ {
+ //================================================================
+ // We can potentially collide with the COM+ activity lock so spawn off
+ // another call that does its stream marshalling on the stack without
+ // the need to do locking.
+ fCallHelper = TRUE;
+ fRetry = FALSE;
+ }
+ }
+
+ if (fCallHelper)
+ {
+ // If we hit a collision earlier, spawn off helper that repeats this operation without locking.
+ pUnk = UnmarshalIUnknownForCurrContextHelper();
+ }
+
+ RETURN pUnk;
+}
+
+//================================================================
+// Release the stream. This will force UnmarshalIUnknownForCurrContext to transition
+// into the context that owns the IP and re-marshal it to the stream.
+void IUnkEntry::ReleaseStream()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // This should release the stream, object in the stream and the memory on which the stream was created
+ if (m_pStream)
+ {
+ SafeReleaseStream(m_pStream);
+ m_pStream = NULL;
+ }
+}
+
+// Indicates if the COM component being wrapped by the IUnkEntry aggreates the FTM
+bool IUnkEntry::IsFreeThreaded()
+{
+ LIMITED_METHOD_CONTRACT;
+ return GetRCW()->IsFreeThreaded();
+}
+
+// Indicates if the COM component being wrapped by the IUnkEntry implements INoMashal.
+bool IUnkEntry::IsMarshalingInhibited()
+{
+ LIMITED_METHOD_CONTRACT;
+ return GetRCW()->IsMarshalingInhibited();
+}
+
+// Helper function to marshal the IUnknown pointer to the stream.
+static HRESULT MarshalIUnknownToStreamHelper(IUnknown * pUnknown, IStream ** ppStream)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ IStream *pStream = NULL;
+
+ GCX_PREEMP();
+
+ // ensure we register this cookie
+ HRESULT hr = wCoMarshalInterThreadInterfaceInStream(IID_IUnknown, pUnknown, &pStream);
+
+ if ((hr == REGDB_E_IIDNOTREG) ||
+ (hr == E_FAIL) ||
+ (hr == E_NOINTERFACE) ||
+ (hr == E_INVALIDARG) ||
+ (hr == E_UNEXPECTED))
+ {
+ // Interface is not marshallable.
+ pStream = NULL;
+ hr = S_OK;
+ }
+
+ *ppStream = pStream;
+
+ return hr;
+}
+
+//================================================================
+struct StreamMarshalData
+{
+ IUnkEntry * m_pUnkEntry;
+ IStream * m_pStream;
+};
+// Fix for if the lock is held
+HRESULT IUnkEntry::MarshalIUnknownToStreamCallback2(LPVOID pData)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(pData));
+ PRECONDITION(g_fProcessDetach == FALSE);
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ StreamMarshalData *psmd = reinterpret_cast<StreamMarshalData *>(pData);
+
+ // This should never be called during process detach.
+
+ hr = psmd->m_pUnkEntry->HRCheckValidIUnkEntry();
+ if (hr != S_OK)
+ {
+ // Interface not marshallable
+ // We'll know marshaling failed because m_pStream == NULL
+ return S_OK;
+ }
+
+ LPVOID pCurrentCtxCookie = GetCurrentCtxCookie();
+ _ASSERTE(pCurrentCtxCookie);
+
+ if (pCurrentCtxCookie == psmd->m_pUnkEntry->m_pCtxCookie)
+ {
+ // We are in the right context marshal the IUnknown to the
+ // stream directly.
+ hr = MarshalIUnknownToStreamHelper(psmd->m_pUnkEntry->m_pUnknown, &psmd->m_pStream);
+ }
+ else
+ {
+ // Transition into the context to marshal the IUnknown to
+ // the stream.
+ _ASSERTE(psmd->m_pUnkEntry->GetCtxEntry() != NULL);
+ hr = psmd->m_pUnkEntry->GetCtxEntry()->EnterContext(MarshalIUnknownToStreamCallback2, psmd);
+ }
+
+ return hr;
+}
+
+//================================================================
+// Unmarshal IUnknown for the current context if the lock is held
+IUnknown* IUnkEntry::UnmarshalIUnknownForCurrContextHelper()
+{
+ CONTRACT (IUnknown*)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(!IsFreeThreaded());
+ POSTCONDITION(CheckPointer(RETVAL));
+ }
+ CONTRACT_END;
+
+ HRESULT hrCDH = S_OK;
+ IUnknown * pUnk = NULL;
+ SafeComHolder<IStream> spStream;
+
+ CheckValidIUnkEntry();
+
+ // Make sure we are in preemptive GC mode before we call out to COM.
+ GCX_PREEMP();
+
+ // Marshal the interface to the stream. Any call to this function
+ // would be from another apartment so marshalling is needed.
+ StreamMarshalData smd = {this, NULL};
+
+ // If context transition failed, we'll return a failure HRESULT
+ // Otherwise, we return S_OK but m_pStream will stay being NULL
+ hrCDH = MarshalIUnknownToStreamCallback2(&smd);
+
+ spStream = smd.m_pStream;
+ smd.m_pStream = NULL;
+
+ CheckForFuncEvalAbort(hrCDH);
+
+#ifdef MDA_SUPPORTED
+ if (FAILED(hrCDH))
+ {
+ MDA_TRIGGER_ASSISTANT(DisconnectedContext, ReportViolationDisconnected(m_pCtxCookie, hrCDH));
+ }
+ else if(spStream == NULL)
+ {
+ MDA_TRIGGER_ASSISTANT(NotMarshalable, ReportViolation());
+ }
+#endif
+
+ // If the interface is not marshalable or if we failed to
+ // enter the context, then we don't have any choice but to
+ // use the raw IP.
+ if (spStream == NULL)
+ {
+ // Give out this IUnknown we are holding
+ pUnk = GetRawIUnknown_NoAddRef();
+
+ RCW_VTABLEPTR(GetRCW());
+ ULONG cbRef = SafeAddRefPreemp(pUnk);
+
+ LogInteropAddRef(pUnk, cbRef, "UnmarshalIUnknownForCurrContext handing out raw IUnknown");
+ }
+ else
+ {
+ // we got control for this entry
+ // GetInterface for the current context
+ HRESULT hr;
+ hr = CoUnmarshalInterface(spStream, IID_IUnknown, reinterpret_cast<void**>(&pUnk));
+ spStream.Release();
+
+ if (FAILED(hr))
+ {
+ CheckForFuncEvalAbort(hr);
+
+ // Give out this IUnknown we are holding
+ pUnk = GetRawIUnknown_NoAddRef();
+
+ RCW_VTABLEPTR(GetRCW());
+ ULONG cbRef = SafeAddRefPreemp(pUnk);
+
+ LogInteropAddRef(pUnk, cbRef, "UnmarshalIUnknownForCurrContext handing out raw IUnknown");
+ }
+ }
+
+ RETURN pUnk;
+}
+
+//================================================================
+// Callback called to marshal the IUnknown into a stream lazily.
+HRESULT IUnkEntry::MarshalIUnknownToStreamCallback(LPVOID pData)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(pData));
+ PRECONDITION(g_fProcessDetach == FALSE);
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ IUnkEntry *pUnkEntry = (IUnkEntry *)pData;
+
+ // This should never be called during process detach.
+
+ hr = pUnkEntry->HRCheckValidIUnkEntry();
+ if (hr != S_OK)
+ {
+ // Interface not marshallable
+ // We'll know marshaling failed because m_pStream == NULL
+ return S_OK;
+ }
+
+ LPVOID pCurrentCtxCookie = GetCurrentCtxCookie();
+ _ASSERTE(pCurrentCtxCookie);
+
+ if (pCurrentCtxCookie == pUnkEntry->m_pCtxCookie)
+ {
+ // We are in the right context marshal the IUnknown to the
+ // stream directly.
+ hr = pUnkEntry->MarshalIUnknownToStream();
+ }
+ else
+ {
+ _ASSERTE(pUnkEntry->GetCtxEntry() != NULL);
+
+ // Transition into the context to marshal the IUnknown to
+ // the stream.
+ hr = pUnkEntry->GetCtxEntry()->EnterContext(MarshalIUnknownToStreamCallback, pUnkEntry);
+ }
+
+ return hr;
+}
+
+//================================================================
+// Helper function to determine if a COM component aggregates the
+// FTM.
+bool IUnkEntry::IsComponentFreeThreaded(IUnknown *pUnk)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(CheckPointer(pUnk));
+ }
+ CONTRACTL_END;
+
+ // First see if the object implements the IAgileObject marker interface
+ SafeComHolderPreemp<IAgileObject> pAgileObject;
+ HRESULT hr = SafeQueryInterfacePreemp(pUnk, IID_IAgileObject, (IUnknown**)&pAgileObject);
+ LogInteropQI(pUnk, IID_IAgileObject, hr, "IUnkEntry::IsComponentFreeThreaded: QI for IAgileObject");
+
+ if (SUCCEEDED(hr))
+ {
+ return true;
+ }
+ else
+ {
+ SafeComHolderPreemp<IMarshal> pMarshal = NULL;
+
+ // If not, then we can try to determine if the component agregates the FTM via IMarshal.
+ hr = SafeQueryInterfacePreemp(pUnk, IID_IMarshal, (IUnknown **)&pMarshal);
+ LogInteropQI(pUnk, IID_IMarshal, hr, "IUnkEntry::IsComponentFreeThreaded: QI for IMarshal");
+ if (SUCCEEDED(hr))
+ {
+ CLSID clsid;
+
+ // The COM component implements IMarshal so we now check to see if the un-marshal class
+ // for this IMarshal is the FTM's un-marshaler.
+ hr = pMarshal->GetUnmarshalClass(IID_IUnknown, NULL, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL, &clsid);
+ if (SUCCEEDED(hr) && clsid == CLSID_InProcFreeMarshaler)
+ {
+ // The un-marshaler is indeed the unmarshaler for the FTM so this object
+ // is free threaded.
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//================================================================
+// Helper function to marshal the IUnknown pointer to the stream.
+HRESULT IUnkEntry::MarshalIUnknownToStream()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+
+ // This must always be called in the right context.
+ PRECONDITION(m_pCtxCookie == GetCurrentCtxCookie());
+ }
+ CONTRACTL_END;
+
+ IStream *pStream = NULL;
+
+ GCX_PREEMP();
+
+ HRESULT hr = S_OK;
+
+ // ensure we register this cookie
+ IUnknown *pUnk = m_pUnknown;
+ if (pUnk == (IUnknown *)0xBADF00D)
+ {
+ hr = COR_E_INVALIDCOMOBJECT;
+ }
+ else
+ {
+ hr = wCoMarshalInterThreadInterfaceInStream(IID_IUnknown, pUnk, &pStream);
+
+ if ((hr == REGDB_E_IIDNOTREG) ||
+ (hr == E_FAIL) ||
+ (hr == E_NOINTERFACE) ||
+ (hr == E_INVALIDARG) ||
+ (hr == E_UNEXPECTED))
+ {
+ // Interface is not marshallable.
+ pStream = NULL;
+ hr = S_OK;
+ }
+ }
+
+ // Try to set the stream in the IUnkEntry. If another thread already set it,
+ // then we need to release the stream we just set up.
+ if (FastInterlockCompareExchangePointer(&m_pStream, pStream, NULL) != NULL)
+ SafeReleaseStream(pStream);
+
+ return hr;
+}
+
+
+// Method to try to start updating the entry.
+bool IUnkEntry::TryUpdateEntry()
+{
+ WRAPPER_NO_CONTRACT;
+
+ CtxEntry *pOldEntry = m_pCtxEntry;
+ if (((DWORD_PTR)pOldEntry & 1) == 0)
+ {
+ CtxEntry *pNewEntry = (CtxEntry *)((DWORD_PTR)pOldEntry | 1);
+ return (InterlockedExchangeT(&m_pCtxEntry, pNewEntry) == pOldEntry);
+ }
+ return false;
+}
+
+// Method to end updating the entry.
+VOID IUnkEntry::EndUpdateEntry()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ CtxEntry *pOldEntry = m_pCtxEntry;
+
+ // we should hold the lock
+ _ASSERTE(((DWORD_PTR)pOldEntry & 1) == 1);
+
+ CtxEntry *pNewEntry = (CtxEntry *)((DWORD_PTR)pOldEntry & ~1);
+
+ // and it's us who resets the bit
+ VERIFY(InterlockedExchangeT(&m_pCtxEntry, pNewEntry) == pOldEntry);
+}
+
+
+#ifdef MDA_SUPPORTED
+
+// Default to a 60 second timeout
+#define MDA_CONTEXT_SWITCH_DEADLOCK_TIMEOUT 60000
+#define MDA_CONTEXT_SWITCH_DEADLOCK_ITERATION_COUNT 1000
+
+struct MDAContextSwitchDeadlockArgs
+{
+ CLREvent* hEvent;
+ LPVOID OriginContext;
+ LPVOID DestinationContext;
+};
+
+DWORD WINAPI MDAContextSwitchDeadlockThreadProc(LPVOID lpParameter)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(CheckPointer(MDA_GET_ASSISTANT(ContextSwitchDeadlock)));
+ }
+ CONTRACTL_END;
+
+ // We need to ensure a thread object has been set up since we will toggle to cooperative GC mode
+ // inside the wait loop.
+ Thread *pThread = SetupThreadNoThrow();
+ if (pThread == NULL)
+ {
+ // If we failed to allocate the thread object, we will skip running the watchdog thread
+ // and simply return.
+ return 0;
+ }
+
+ DWORD retval = 0;
+ NewHolder<MDAContextSwitchDeadlockArgs> args = (MDAContextSwitchDeadlockArgs*)lpParameter;
+
+ // This interesting piece of code allows us to avoid firing the MDA when a process is
+ // being debugged while the context transition is in progress. It is needed because
+ // CLREvent::Wait will timeout after the specified amount of wall time, even if the
+ // process is broken into the debugger for a portion of the time. By splitting the
+ // wait into a bunch of smaller waits, we allow many more step/continue operations to
+ // occur before we signal the timeout.
+ for (int i = 0; i < MDA_CONTEXT_SWITCH_DEADLOCK_ITERATION_COUNT; i++)
+ {
+ retval = args->hEvent->Wait(MDA_CONTEXT_SWITCH_DEADLOCK_TIMEOUT / MDA_CONTEXT_SWITCH_DEADLOCK_ITERATION_COUNT, FALSE);
+ if (retval != WAIT_TIMEOUT)
+ break;
+
+ // Transition to cooperative GC mode and back. This will allow us to stop the timeout
+ // if we are broken in while managed only debugging.
+ {
+ GCX_COOP();
+ }
+ }
+
+ if (retval == WAIT_TIMEOUT)
+ {
+ // We didn't transition into the context within the alloted timeout period.
+ // We'll fire the mda and close the event, but we can't delete is as the
+ // thread may still complete the transition and attempt to signal the event.
+ // So we'll just leak it and let the transition thread recognize that the
+ // event is no longer valid so it can simply delete it.
+ MDA_TRIGGER_ASSISTANT(ContextSwitchDeadlock, ReportDeadlock(args->OriginContext, args->DestinationContext));
+
+ args->hEvent->CloseEvent();
+ return 1;
+ }
+
+ delete args->hEvent;
+
+ return 0;
+}
+
+
+void QueueMDAThread(CtxEntryEnterContextCallbackData* data)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(data->m_hTimeoutEvent));
+ }
+ CONTRACTL_END;
+
+ MDAContextSwitchDeadlockArgs* args = NULL;
+
+ EX_TRY
+ {
+ args = new MDAContextSwitchDeadlockArgs;
+
+ args->OriginContext = GetCurrentCtxCookie();
+ args->DestinationContext = data->m_pCtxCookie;
+ args->hEvent = data->m_hTimeoutEvent;
+
+ // Will execute in the Default AppDomain
+ ThreadpoolMgr::QueueUserWorkItem(MDAContextSwitchDeadlockThreadProc, (LPVOID)args, WT_EXECUTELONGFUNCTION);
+ }
+ EX_CATCH
+ {
+ delete data->m_hTimeoutEvent;
+ data->m_hTimeoutEvent = NULL;
+
+ if (args)
+ delete args;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+}
+
+#endif // MDA_SUPPORTED
+
+
+// Initialize the entry, returns true on success (i.e. the entry was free).
+bool InterfaceEntry::Init(MethodTable* pMT, IUnknown* pUnk)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // It is important the fields be set in this order.
+ if (InterlockedCompareExchangeT(&m_pUnknown, pUnk, NULL) == NULL)
+ {
+ m_pMT = (IE_METHODTABLE_PTR)pMT;
+ return true;
+ }
+ return false;
+}
+
+// Helper to determine if the entry is free.
+BOOL InterfaceEntry::IsFree()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_pUnknown.Load() == NULL;
+}
+
+void InterfaceEntry::Free()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // We use the m_pUnknown field to synchronize access to the entry so that's the only
+ // one we need to reset. After all, the set of interfaces that the object is known to
+ // support is one of the most important debugging cues so let's keep m_pMT intact.
+ m_pUnknown.Store(NULL);
+}
+
+//================================================================
+// Constructor for the context entry.
+CtxEntry::CtxEntry(LPVOID pCtxCookie, Thread *pSTAThread)
+: m_pCtxCookie(pCtxCookie)
+, m_pObjCtx(NULL)
+, m_dwRefCount(0)
+, m_pSTAThread(pSTAThread)
+{
+ WRAPPER_NO_CONTRACT;
+}
+
+//================================================================
+// Destructor for the context entry.
+CtxEntry::~CtxEntry()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(m_dwRefCount == 0);
+ }
+ CONTRACTL_END;
+
+ // If the context is a valid context then release it.
+ if (m_pObjCtx && !g_fProcessDetach)
+ {
+ SafeRelease(m_pObjCtx);
+ m_pObjCtx = NULL;
+ }
+
+ // Set the context cookie to 0xBADF00D to indicate the current context
+ // has been deleted.
+ m_pCtxCookie = (LPVOID)0xBADF00D;
+}
+
+//================================================================
+// Initialization method for the context entry.
+VOID CtxEntry::Init()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ INJECT_FAULT(COMPlusThrowOM());
+
+ // Make sure COM has been started
+ PRECONDITION(g_fComStarted == TRUE);
+ }
+ CONTRACTL_END;
+
+ // Retrieve the IObjectContext.
+ HRESULT hr = GetCurrentObjCtx(&m_pObjCtx);
+
+ // In case the call to GetCurrentObjCtx fails (which should never really happen)
+ // we will throw an exception.
+ if (FAILED(hr))
+ COMPlusThrowHR(hr);
+}
+
+
+// Add a reference to the CtxEntry.
+DWORD CtxEntry::AddRef()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ ULONG cbRef = FastInterlockIncrement((LONG*)&m_dwRefCount);
+ LOG((LF_INTEROP, LL_INFO100, "CtxEntry::Addref %8.8x with %d\n", this, cbRef));
+ return cbRef;
+}
+
+
+//================================================================
+// Method to decrement the ref count of the context entry.
+DWORD CtxEntry::Release()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ PRECONDITION(m_dwRefCount > 0);
+ }
+ CONTRACTL_END;
+
+ LPVOID pCtxCookie = m_pCtxCookie;
+
+ LONG cbRef = FastInterlockDecrement((LONG*)&m_dwRefCount);
+ LOG((LF_INTEROP, LL_INFO100, "CtxEntry::Release %8.8x with %d\n", this, cbRef));
+
+ // If the ref count falls to 0, try and delete the ctx entry.
+ // This might not end up deleting it if another thread tries to
+ // retrieve this ctx entry at the same time this one tries
+ // to delete it.
+ if (cbRef == 0)
+ CtxEntryCache::GetCtxEntryCache()->TryDeleteCtxEntry(pCtxCookie);
+
+ // WARNING: The this pointer cannot be used at this point.
+ return cbRef;
+}
+
+//================================================================
+// Method to transition into the context and call the callback
+// from within the context.
+HRESULT CtxEntry::EnterContext(PFNCTXCALLBACK pCallbackFunc, LPVOID pData)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(pCallbackFunc));
+ PRECONDITION(CheckPointer(pData));
+ // This should not be called if the this context is the current context.
+ PRECONDITION(m_pCtxCookie != GetCurrentCtxCookie());
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // If we are in process detach, we cannot safely try to enter another context
+ // since we don't know if OLE32 is still loaded.
+ if (g_fProcessDetach)
+ {
+ LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08%x has failed since we are in process detach\n", m_pCtxCookie));
+ return RPC_E_DISCONNECTED;
+ }
+
+ // Make sure we are in preemptive GC mode before we call out to COM.
+ GCX_PREEMP();
+
+ // Prepare the information struct passed into the callback.
+ CtxEntryEnterContextCallbackData CallbackInfo;
+ CallbackInfo.m_pUserCallbackFunc = pCallbackFunc;
+ CallbackInfo.m_pUserData = pData;
+ CallbackInfo.m_pCtxCookie = m_pCtxCookie;
+ CallbackInfo.m_UserCallbackHR = E_FAIL;
+#ifdef MDA_SUPPORTED
+ CallbackInfo.m_hTimeoutEvent = NULL;
+
+ MdaContextSwitchDeadlock* mda = MDA_GET_ASSISTANT(ContextSwitchDeadlock);
+ if (mda)
+ {
+ EX_TRY
+ {
+ CallbackInfo.m_hTimeoutEvent = new CLREvent();
+ CallbackInfo.m_hTimeoutEvent->CreateAutoEvent(FALSE);
+ }
+ EX_CATCH
+ {
+ if (CallbackInfo.m_hTimeoutEvent)
+ {
+ delete CallbackInfo.m_hTimeoutEvent;
+ CallbackInfo.m_hTimeoutEvent = NULL;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+#endif // MDA_SUPPORTED
+
+ // Retrieve the IContextCallback interface from the IObjectContext.
+ SafeComHolderPreemp<IContextCallback> pCallback;
+ hr = SafeQueryInterfacePreemp(m_pObjCtx, IID_IContextCallback, (IUnknown**)&pCallback);
+ LogInteropQI(m_pObjCtx, IID_IContextCallback, hr, "QI for IID_IContextCallback");
+ _ASSERTE(SUCCEEDED(hr) && pCallback);
+
+ // Setup the callback data structure with the callback Args
+ ComCallData callBackData;
+ callBackData.dwDispid = 0;
+ callBackData.dwReserved = 0;
+ callBackData.pUserDefined = &CallbackInfo;
+
+#ifdef MDA_SUPPORTED
+ // Make sure we don't deadlock when trying to enter the context.
+ if (mda && CallbackInfo.m_hTimeoutEvent)
+ {
+ QueueMDAThread(&CallbackInfo);
+ }
+#endif
+
+ EX_TRY
+ {
+ LeaveRuntimeHolder lrHolder(**(size_t**)(IContextCallback*)pCallback);
+ hr = ((IContextCallback*)pCallback)->ContextCallback(EnterContextCallback, &callBackData, IID_IEnterActivityWithNoLock, 2, NULL);
+ }
+ EX_CATCH
+ {
+ hr = GET_EXCEPTION()->GetHR();
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ if (FAILED(hr))
+ {
+ // If the transition failed because of an aborted func eval, simply propagate
+ // the HRESULT/IErrorInfo back to the caller as we cannot throw here.
+ SafeComHolder<IErrorInfo> pErrorInfo = CheckForFuncEvalAbortNoThrow(hr);
+ if (pErrorInfo != NULL)
+ {
+ LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08X has failed since the debugger is blocking it\n", m_pCtxCookie));
+
+ // put the IErrorInfo back
+ {
+ LeaveRuntimeHolderNoThrow lrh((size_t)SetErrorInfo);
+ if (SUCCEEDED(lrh.GetHR()))
+ {
+ SetErrorInfo(0, pErrorInfo);
+ }
+ }
+ }
+ else
+ {
+ // The context is disconnected so we cannot transition into it.
+ LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08X has failed since the context has disconnected\n", m_pCtxCookie));
+ }
+ }
+
+ return hr;
+}
+
+
+//================================================================
+// Callback function called by DoCallback.
+HRESULT __stdcall CtxEntry::EnterContextCallback(ComCallData* pComCallData)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ SO_NOT_MAINLINE;
+ PRECONDITION(CheckPointer(pComCallData));
+ }
+ CONTRACTL_END;
+
+ // Retrieve the callback data.
+ CtxEntryEnterContextCallbackData *pData = (CtxEntryEnterContextCallbackData*)pComCallData->pUserDefined;
+
+
+#ifdef MDA_SUPPORTED
+ // If active, signal the MDA watcher so we don't accidentally trigger a timeout.
+ MdaContextSwitchDeadlock* mda = MDA_GET_ASSISTANT(ContextSwitchDeadlock);
+ if (mda)
+ {
+ // If our watchdog worker is still waiting on us, the event will be valid.
+ if (pData->m_hTimeoutEvent->IsValid())
+ {
+ pData->m_hTimeoutEvent->Set();
+ }
+ else
+ {
+ // If we did timeout, we will have already cleaned up the event...just delete it now.
+ delete pData->m_hTimeoutEvent;
+ }
+ }
+#endif // MDA_SUPPORTED
+
+ Thread *pThread = GetThread();
+
+ // Make sure the thread has been set before we call the user callback function.
+ if (!pThread)
+ {
+ // huh! we are in the middle of shutdown
+ // and there is no way we can add a new thread
+ // so let us just return RPC_E_DISCONNECTED
+ // look at the pCallBack->DoCallback above
+ // to see why we are returning this SCODE
+ if(g_fEEShutDown)
+ return RPC_E_DISCONNECTED;
+
+ // Otherwise, we need to create a managed thread object for this new thread
+ else
+ {
+ HRESULT hr;
+ pThread = SetupThreadNoThrow(&hr);
+ if (pThread == NULL)
+ return hr;
+ }
+ }
+
+ // at this point we should be in the right context on NT4,
+ // if not then it is possible that the actual apartment state for this
+ // thread has changed and we have stale info in our thread or the CtxEntry
+ LPVOID pCtxCookie = GetCurrentCtxCookie();
+ _ASSERTE(pCtxCookie);
+ if (pData->m_pCtxCookie != pCtxCookie)
+ return RPC_E_DISCONNECTED;
+
+ // Call the user callback function and store the return value the
+ // callback data.
+ pData->m_UserCallbackHR = pData->m_pUserCallbackFunc(pData->m_pUserData);
+
+ // Return S_OK to indicate the context transition was successfull.
+ return S_OK;
+}