1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
11 #include "runtimecallablewrapper.h"
15 #include "win32threadpool.h"
16 #include "mdaassistants.h"
18 #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT
19 #include "olecontexthelpers.h"
20 #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT
23 //================================================================
25 const GUID IID_IEnterActivityWithNoLock = { 0xd7174f82, 0x36b8, 0x4aa8, { 0x80, 0x0a, 0xe9, 0x63, 0xab, 0x2d, 0xfa, 0xb9 } };
26 const GUID IID_IFuncEvalAbort = { 0xde6844f6, 0x95ac, 0x4e83, { 0x90, 0x8d, 0x9b, 0x1b, 0xea, 0x2f, 0xe0, 0x8c } };
28 // sanity check., to find stress bug #82137
29 VOID IUnkEntry::CheckValidIUnkEntry()
41 COMPlusThrow(kInvalidComObjectException, IDS_EE_COM_OBJECT_NO_LONGER_HAS_WRAPPER);
45 // Version that returns an HR instead of throwing.
46 HRESULT IUnkEntry::HRCheckValidIUnkEntry()
58 return COR_E_INVALIDCOMOBJECT;
64 // Returns IErrorInfo corresponding to the exception injected by the debugger to abort a func eval,
65 // or NULL if there is no such exception.
66 static IErrorInfo *CheckForFuncEvalAbortNoThrow(HRESULT hr)
76 // the managed exception thrown by the debugger is translated to EXCEPTION_COMPLUS by COM
77 if (hr == EXCEPTION_COMPLUS)
81 // we recognize the ones thrown by the debugger by QI'ing the IErrorInfo for a special IID
82 ReleaseHolder<IErrorInfo> pErrorInfo;
83 if (SafeGetErrorInfo(&pErrorInfo) == S_OK)
85 ReleaseHolder<IUnknown> pUnk;
86 if (SafeQueryInterface(pErrorInfo, IID_IFuncEvalAbort, &pUnk) == S_OK)
88 // QI succeeded, this is a func eval abort
89 return pErrorInfo.Extract();
93 // QI failed, put the IErrorInfo back
94 SetErrorInfo(0, pErrorInfo);
102 // Rethrows the exception injected by the debugger to abort a func eval, or does nothing if there is no such exception.
103 static void CheckForFuncEvalAbort(HRESULT hr)
113 IErrorInfo *pErrorInfo = CheckForFuncEvalAbortNoThrow(hr);
114 if (pErrorInfo != NULL)
116 // COMPlusThrowHR internally releases the pErrorInfo
117 COMPlusThrowHR(hr, pErrorInfo);
121 //+-------------------------------------------------------------------------
123 // Function: STDAPI_(LPSTREAM) CreateMemStm(DWORD cb, BYTE** ppBuf))
124 // Create a stream in the memory
126 STDAPI_(LPSTREAM) CreateMemStm(DWORD cb, BYTE** ppBuf)
133 INJECT_FAULT(CONTRACT_RETURN NULL);
134 PRECONDITION(CheckPointer(ppBuf, NULL_OK));
135 PRECONDITION(CheckPointer(ppBuf, NULL_OK));
136 POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
140 LPSTREAM pstm = NULL;
142 BYTE* pMem = new(nothrow) BYTE[cb];
145 HRESULT hr = CInMemoryStream::CreateStreamOnMemory(pMem, cb, &pstm, TRUE);
146 _ASSERTE(hr == S_OK || pstm == NULL);
155 //=====================================================================
156 // HRESULT wCoMarshalInterThreadInterfaceInStream
157 HRESULT wCoMarshalInterThreadInterfaceInStream(REFIID riid,
163 #endif // !PLATFORM_CE
170 PRECONDITION(CheckPointer(pUnk));
171 PRECONDITION(CheckPointer(ppStm));
176 LPSTREAM pStm = NULL;
178 DWORD mshlFlgs = MSHLFLAGS_NORMAL;
181 hr = CoGetMarshalSizeMax(&lSize, IID_IUnknown, pUnk, MSHCTX_INPROC, NULL, mshlFlgs);
186 pStm = CreateMemStm(lSize, NULL);
190 // Marshal the interface into the stream TABLE STRONG
191 hr = CoMarshalInterface(pStm, riid, pUnk, MSHCTX_INPROC, NULL, mshlFlgs);
201 // Reset the stream to the begining
205 pStm->Seek(li, STREAM_SEEK_SET, &li2);
207 // Set the return value
212 // Cleanup if failure
213 SafeReleasePreemp(pStm);
221 //================================================================
222 // Struct passed in to DoCallback.
223 struct CtxEntryEnterContextCallbackData
225 PFNCTXCALLBACK m_pUserCallbackFunc;
228 HRESULT m_UserCallbackHR;
230 CLREvent* m_hTimeoutEvent;
234 //================================================================
236 CtxEntryCache* CtxEntryCache::s_pCtxEntryCache = NULL;
238 CtxEntryCache::CtxEntryCache()
248 m_Lock.Init(LOCK_COMCTXENTRYCACHE);
249 LockOwner lock = {&m_Lock, IsOwnerOfSpinLock};
252 CtxEntryCache::~CtxEntryCache()
262 for (SHash<CtxEntryCacheTraits>::Iterator it = m_CtxEntryHash.Begin(); it != m_CtxEntryHash.End(); it++)
264 CtxEntry *pCtxEntry = (CtxEntry *)*it;
266 LPVOID CtxCookie = pCtxEntry->GetCtxCookie();
267 m_CtxEntryHash.Remove(CtxCookie);
269 LOG((LF_INTEROP, LL_INFO100, "Leaked CtxEntry %8.8x with CtxCookie %8.8x, ref count %d\n", pCtxEntry, pCtxEntry->GetCtxCookie(), pCtxEntry->m_dwRefCount));
270 pCtxEntry->m_dwRefCount = 0;
276 void CtxEntryCache::Init()
284 // This should never be called more than once.
285 PRECONDITION(NULL == s_pCtxEntryCache);
289 // Allocate the one and only instance of the context entry cache.
290 s_pCtxEntryCache = new CtxEntryCache();
293 CtxEntryCache* CtxEntryCache::GetCtxEntryCache()
300 PRECONDITION(CheckPointer(s_pCtxEntryCache));
304 return s_pCtxEntryCache;
307 CtxEntry* CtxEntryCache::CreateCtxEntry(LPVOID pCtxCookie, Thread * pSTAThread)
317 CtxEntry * pCtxEntry = NULL;
318 Thread * pThread = GetThread();
320 // If we don't already have a context entry for the context cookie,
321 // we need to create one.
322 NewHolder<CtxEntry> pNewCtxEntry = new CtxEntry(pCtxCookie, pSTAThread);
323 // tiggers GC, can't happen when we hold spin lock
324 pNewCtxEntry->Init();
327 TAKE_SPINLOCK_AND_DONOT_TRIGGER_GC(&m_Lock);
329 // double check for race
330 pCtxEntry = m_CtxEntryHash.Lookup(pCtxCookie);
331 if (pCtxEntry == NULL)
333 // We successfully allocated and initialized the entry.
334 m_CtxEntryHash.Add(pNewCtxEntry);
335 pCtxEntry = pNewCtxEntry.Extract();
337 // We must have an entry now; we need to addref it before
338 // we leave the lock.
339 pCtxEntry->AddRef ();
345 CtxEntry* CtxEntryCache::FindCtxEntry(LPVOID pCtxCookie, Thread *pThread)
347 CtxEntry *pCtxEntry = NULL;
348 Thread *pSTAThread = NULL;
355 INJECT_FAULT(COMPlusThrowOM());
356 PRECONDITION(CheckPointer(pCtxCookie));
357 POSTCONDITION(CheckPointer(RETVAL));
358 POSTCONDITION(pCtxCookie == pCtxEntry->GetCtxCookie());
359 POSTCONDITION(pSTAThread == pCtxEntry->GetSTAThread());
363 // Find our STA (if any)
364 if (pThread->GetApartment() == Thread::AS_InSTA)
366 // We are in an STA thread. But we may be in a NA context, so do an extra
367 // check for that case.
370 // try the simple cache on Thread first
371 if (pCtxCookie != pThread->GetLastSTACtxCookie(&fNAContext))
374 fNAContext = (SUCCEEDED(GetCurrentApartmentTypeNT5((IObjectContext *)pCtxCookie, &type)) && type == APTTYPE_NA);
375 pThread->SetLastSTACtxCookie(pCtxCookie, fNAContext);
379 pSTAThread = pThread;
382 ASSERT (GetThread ());
385 ACQUIRE_SPINLOCK_NO_HOLDER(&m_Lock, pThread);
387 // Try to find a context entry for the context cookie.
388 pCtxEntry = m_CtxEntryHash.Lookup(pCtxCookie);
391 // We must have an entry now; we need to addref it before
392 // we leave the lock.
393 pCtxEntry->AddRef ();
397 RELEASE_SPINLOCK_NO_HOLDER(&m_Lock, pThread);
401 pCtxEntry = CreateCtxEntry(pCtxCookie, pSTAThread);
404 // Returned the found or allocated entry.
409 void CtxEntryCache::TryDeleteCtxEntry(LPVOID pCtxCookie)
416 PRECONDITION(CheckPointer(pCtxCookie));
420 BOOL bDelete = FALSE;
421 CtxEntry *pCtxEntry = NULL;
424 TAKE_SPINLOCK_AND_DONOT_TRIGGER_GC(&m_Lock);
426 // Try to find a context entry for the context cookie.
427 pCtxEntry = m_CtxEntryHash.Lookup(pCtxCookie);
430 // If the ref count of the context entry is still 0, then we can
431 // remove the ctx entry and delete it.
432 if (pCtxEntry->m_dwRefCount == 0)
434 // First remove the context entry from the list.
435 m_CtxEntryHash.Remove(pCtxCookie);
437 // We need to unlock the context entry cache before we delete the
438 // context entry since this can cause release to be called on
439 // an IP which can cause us to re-enter the runtime thus causing a
441 // We can now safely delete the context entry.
453 //================================================================
454 // Get the RCW associated with this IUnkEntry
455 // We assert inside Init that this IUnkEntry is indeed within a RCW
456 RCW *IUnkEntry::GetRCW()
458 LIMITED_METHOD_CONTRACT;
460 return (RCW *) (((LPBYTE) this) - offsetof(RCW, m_UnkEntry));
463 //================================================================
464 // Initialize the entry
465 void IUnkEntry::Init(
467 BOOL bIsFreeThreaded,
477 PRECONDITION(CheckPointer(pUnk));
478 INDEBUG(PRECONDITION(CheckPointer(pRCW));)
482 // Make sure this IUnkEntry is part of a RCW so that we can get back to the RCW through offset
484 _ASSERTE(((LPBYTE)pRCW) + offsetof(RCW, m_UnkEntry) == (LPBYTE) this);
486 // Find our context cookie
487 LPVOID pCtxCookie = GetCurrentCtxCookie();
488 _ASSERTE(pCtxCookie);
490 // Set up IUnkEntry's state.
494 m_pCtxEntry = CtxEntryCache::GetCtxEntryCache()->FindCtxEntry(pCtxCookie, pThread);
497 m_pCtxCookie = pCtxCookie;
500 // Sanity check this IUnkEntry.
501 CheckValidIUnkEntry();
504 //================================================================
505 // Release the interface pointer held by the IUnkEntry.
506 VOID IUnkEntry::ReleaseInterface(RCW *pRCW)
516 if (g_fProcessDetach)
518 // The Release call is unsafe if the process is going away (calls into
519 // DLLs we don't know are even mapped).
520 LogInteropLeak(this);
524 // now release the IUnknown that we hold
525 if ((m_pUnknown != 0) && (m_pUnknown != (IUnknown *)0xBADF00D))
527 ULONG cbRef = SafeReleasePreemp(m_pUnknown, pRCW);
528 LogInteropRelease(m_pUnknown, cbRef, "IUnkEntry::Free: Releasing the held ref");
531 // mark the entry as dead
532 m_pUnknown = (IUnknown *)0xBADF00D;
536 //================================================================
537 // Free the IUnknown entry. ReleaseInterface must have been called.
538 VOID IUnkEntry::Free()
545 PRECONDITION(g_fProcessDetach || m_pUnknown == (IUnknown *)0xBADF00D);
549 // Log the de-allocation of the IUnknown entry.
550 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()));
552 if (g_fProcessDetach)
554 IStream *pOldStream = m_pStream;
555 if (InterlockedExchangeT(&m_pStream, NULL) == pOldStream)
556 SafeReleasePreemp(pOldStream);
560 IStream *pStream = m_pStream;
563 // This will release the stream, object in the stream and the memory on which the stream was created
565 SafeReleaseStream(pStream);
569 // Release the ref count we have on the CtxEntry.
570 CtxEntry *pEntry = GetCtxEntry();
578 //================================================================
579 // Get IUnknown for the current context from IUnkEntry
580 IUnknown* IUnkEntry::GetIUnknownForCurrContext(bool fNoAddRef)
587 POSTCONDITION(CheckPointer(RETVAL, (fNoAddRef ? NULL_OK : NULL_NOT_OK)));
591 IUnknown* pUnk = NULL;
592 LPVOID pCtxCookie = GetCurrentCtxCookie();
593 _ASSERTE(pCtxCookie);
595 CheckValidIUnkEntry();
597 if (m_pCtxCookie == pCtxCookie || IsFreeThreaded())
599 pUnk = GetRawIUnknown_NoAddRef();
603 RCW_VTABLEPTR(GetRCW());
604 ULONG cbRef = SafeAddRef(pUnk);
605 LogInteropAddRef(pUnk, cbRef, "IUnkEntry::GetIUnknownForCurrContext: Addref pUnk, passing ref to caller");
609 if (pUnk == NULL && !fNoAddRef)
610 pUnk = UnmarshalIUnknownForCurrContext();
615 //================================================================
616 // Unmarshal IUnknown for the current context from IUnkEntry
617 IUnknown* IUnkEntry::UnmarshalIUnknownForCurrContext()
624 PRECONDITION(!IsFreeThreaded());
625 POSTCONDITION(CheckPointer(RETVAL));
629 HRESULT hrCDH = S_OK;
630 IUnknown* pUnk = NULL;
632 BOOL fUnmarshalFailed = FALSE;
633 BOOL fCallHelper = FALSE;
635 CheckValidIUnkEntry();
637 _ASSERTE(GetCtxEntry() != NULL);
640 if(IsMarshalingInhibited() && (m_pCtxCookie != GetCurrentCtxCookie()))
642 // We want to use an interface in a different context but it can't be marshalled.
643 LOG((LF_INTEROP, LL_INFO100, "IUnkEntry::GetIUnknownForCurrContext failed as the COM object has inhibited marshaling"));
644 COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST_NOMARSHAL);
647 // Make sure we are in preemptive GC mode before we call out to COM.
650 // Need to synchronize
653 // Marshal the interface to the stream if it hasn't been done yet.
654 if (m_pStream == NULL)
656 // If context transition failed, we'll return a failure HRESULT
657 // Otherwise, we return S_OK but m_pStream will stay being NULL
658 hrCDH = MarshalIUnknownToStreamCallback(this);
659 CheckForFuncEvalAbort(hrCDH);
664 MDA_TRIGGER_ASSISTANT(DisconnectedContext, ReportViolationDisconnected(m_pCtxCookie, hrCDH));
666 else if (m_pStream == NULL)
668 MDA_TRIGGER_ASSISTANT(NotMarshalable, ReportViolation());
673 if (TryUpdateEntry())
675 // If the interface is not marshalable or if we failed to
676 // enter the context, then we don't have any choice but to
678 if (m_pStream == NULL)
680 // We retrieved an IP so stop retrying.
683 // Give out this IUnknown we are holding
684 pUnk = GetRawIUnknown_NoAddRef();
686 RCW_VTABLEPTR(GetRCW());
687 ULONG cbRef = SafeAddRefPreemp(pUnk);
689 LogInteropAddRef(pUnk, cbRef, "UnmarshalIUnknownForCurrContext handing out raw IUnknown");
693 // we got control for this entry
694 // GetInterface for the current context
696 hr = CoUnmarshalInterface(m_pStream, IID_IUnknown, (void **)&pUnk);
698 // If the objref in the stream times out, we need to go an marshal into the
699 // stream once again.
704 CheckForFuncEvalAbort(hr);
706 // This should release the stream, object in the stream and the memory on which the stream was created
707 SafeReleaseStream(m_pStream);
710 // If unmarshal failed twice, then bail out.
711 if (fUnmarshalFailed)
715 // Handing out m_pUnknown in this case would be incorrect. We should fix other places that are doing the same thing in Dev10
716 // To minimize code changes, throwing E_NOINTERFACE instead
717 COMPlusThrowHR(E_NOINTERFACE);
720 // Remember we failed to unmarshal.
721 fUnmarshalFailed = TRUE;
725 // Reset the stream to the begining
729 m_pStream->Seek(li, STREAM_SEEK_SET, &li2);
731 // Marshal the interface into the stream with appropriate flags
732 hr = CoMarshalInterface(m_pStream,
733 IID_IUnknown, pUnk, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL);
737 CheckForFuncEvalAbort(hr);
739 // The proxy is no longer valid. This sometimes manifests itself by
740 // a failure during re-marshaling it to the stream. When this happens,
741 // we need to release the the pUnk we extracted and the stream and try to
742 // re-create the stream. We don't want to release the stream data since
743 // we already extracted the proxy from the stream and released it.
744 RCW_VTABLEPTR(GetRCW());
745 SafeReleasePreemp(pUnk);
747 SafeReleasePreemp(m_pStream);
752 // Reset the stream to the begining
754 m_pStream->Seek(li, STREAM_SEEK_SET, &li2);
756 // We managed to unmarshal the IP from the stream, stop retrying.
762 // Done with the entry.
767 //================================================================
768 // We can potentially collide with the COM+ activity lock so spawn off
769 // another call that does its stream marshalling on the stack without
770 // the need to do locking.
778 // If we hit a collision earlier, spawn off helper that repeats this operation without locking.
779 pUnk = UnmarshalIUnknownForCurrContextHelper();
785 //================================================================
786 // Release the stream. This will force UnmarshalIUnknownForCurrContext to transition
787 // into the context that owns the IP and re-marshal it to the stream.
788 void IUnkEntry::ReleaseStream()
798 // This should release the stream, object in the stream and the memory on which the stream was created
801 SafeReleaseStream(m_pStream);
806 // Indicates if the COM component being wrapped by the IUnkEntry aggreates the FTM
807 bool IUnkEntry::IsFreeThreaded()
809 LIMITED_METHOD_CONTRACT;
810 return GetRCW()->IsFreeThreaded();
813 // Indicates if the COM component being wrapped by the IUnkEntry implements INoMashal.
814 bool IUnkEntry::IsMarshalingInhibited()
816 LIMITED_METHOD_CONTRACT;
817 return GetRCW()->IsMarshalingInhibited();
820 // Helper function to marshal the IUnknown pointer to the stream.
821 static HRESULT MarshalIUnknownToStreamHelper(IUnknown * pUnknown, IStream ** ppStream)
831 IStream *pStream = NULL;
835 // ensure we register this cookie
836 HRESULT hr = wCoMarshalInterThreadInterfaceInStream(IID_IUnknown, pUnknown, &pStream);
838 if ((hr == REGDB_E_IIDNOTREG) ||
840 (hr == E_NOINTERFACE) ||
841 (hr == E_INVALIDARG) ||
842 (hr == E_UNEXPECTED))
844 // Interface is not marshallable.
854 //================================================================
855 struct StreamMarshalData
857 IUnkEntry * m_pUnkEntry;
860 // Fix for if the lock is held
861 HRESULT IUnkEntry::MarshalIUnknownToStreamCallback2(LPVOID pData)
868 PRECONDITION(CheckPointer(pData));
869 PRECONDITION(g_fProcessDetach == FALSE);
874 StreamMarshalData *psmd = reinterpret_cast<StreamMarshalData *>(pData);
876 // This should never be called during process detach.
878 hr = psmd->m_pUnkEntry->HRCheckValidIUnkEntry();
881 // Interface not marshallable
882 // We'll know marshaling failed because m_pStream == NULL
886 LPVOID pCurrentCtxCookie = GetCurrentCtxCookie();
887 _ASSERTE(pCurrentCtxCookie);
889 if (pCurrentCtxCookie == psmd->m_pUnkEntry->m_pCtxCookie)
891 // We are in the right context marshal the IUnknown to the
893 hr = MarshalIUnknownToStreamHelper(psmd->m_pUnkEntry->m_pUnknown, &psmd->m_pStream);
897 // Transition into the context to marshal the IUnknown to
899 _ASSERTE(psmd->m_pUnkEntry->GetCtxEntry() != NULL);
900 hr = psmd->m_pUnkEntry->GetCtxEntry()->EnterContext(MarshalIUnknownToStreamCallback2, psmd);
906 //================================================================
907 // Unmarshal IUnknown for the current context if the lock is held
908 IUnknown* IUnkEntry::UnmarshalIUnknownForCurrContextHelper()
915 PRECONDITION(!IsFreeThreaded());
916 POSTCONDITION(CheckPointer(RETVAL));
920 HRESULT hrCDH = S_OK;
921 IUnknown * pUnk = NULL;
922 SafeComHolder<IStream> spStream;
924 CheckValidIUnkEntry();
926 // Make sure we are in preemptive GC mode before we call out to COM.
929 // Marshal the interface to the stream. Any call to this function
930 // would be from another apartment so marshalling is needed.
931 StreamMarshalData smd = {this, NULL};
933 // If context transition failed, we'll return a failure HRESULT
934 // Otherwise, we return S_OK but m_pStream will stay being NULL
935 hrCDH = MarshalIUnknownToStreamCallback2(&smd);
937 spStream = smd.m_pStream;
938 smd.m_pStream = NULL;
940 CheckForFuncEvalAbort(hrCDH);
945 MDA_TRIGGER_ASSISTANT(DisconnectedContext, ReportViolationDisconnected(m_pCtxCookie, hrCDH));
947 else if(spStream == NULL)
949 MDA_TRIGGER_ASSISTANT(NotMarshalable, ReportViolation());
953 // If the interface is not marshalable or if we failed to
954 // enter the context, then we don't have any choice but to
956 if (spStream == NULL)
958 // Give out this IUnknown we are holding
959 pUnk = GetRawIUnknown_NoAddRef();
961 RCW_VTABLEPTR(GetRCW());
962 ULONG cbRef = SafeAddRefPreemp(pUnk);
964 LogInteropAddRef(pUnk, cbRef, "UnmarshalIUnknownForCurrContext handing out raw IUnknown");
968 // we got control for this entry
969 // GetInterface for the current context
971 hr = CoUnmarshalInterface(spStream, IID_IUnknown, reinterpret_cast<void**>(&pUnk));
976 CheckForFuncEvalAbort(hr);
978 // Give out this IUnknown we are holding
979 pUnk = GetRawIUnknown_NoAddRef();
981 RCW_VTABLEPTR(GetRCW());
982 ULONG cbRef = SafeAddRefPreemp(pUnk);
984 LogInteropAddRef(pUnk, cbRef, "UnmarshalIUnknownForCurrContext handing out raw IUnknown");
991 //================================================================
992 // Callback called to marshal the IUnknown into a stream lazily.
993 HRESULT IUnkEntry::MarshalIUnknownToStreamCallback(LPVOID pData)
1000 PRECONDITION(CheckPointer(pData));
1001 PRECONDITION(g_fProcessDetach == FALSE);
1006 IUnkEntry *pUnkEntry = (IUnkEntry *)pData;
1008 // This should never be called during process detach.
1010 hr = pUnkEntry->HRCheckValidIUnkEntry();
1013 // Interface not marshallable
1014 // We'll know marshaling failed because m_pStream == NULL
1018 LPVOID pCurrentCtxCookie = GetCurrentCtxCookie();
1019 _ASSERTE(pCurrentCtxCookie);
1021 if (pCurrentCtxCookie == pUnkEntry->m_pCtxCookie)
1023 // We are in the right context marshal the IUnknown to the
1025 hr = pUnkEntry->MarshalIUnknownToStream();
1029 _ASSERTE(pUnkEntry->GetCtxEntry() != NULL);
1031 // Transition into the context to marshal the IUnknown to
1033 hr = pUnkEntry->GetCtxEntry()->EnterContext(MarshalIUnknownToStreamCallback, pUnkEntry);
1039 //================================================================
1040 // Helper function to determine if a COM component aggregates the
1042 bool IUnkEntry::IsComponentFreeThreaded(IUnknown *pUnk)
1049 PRECONDITION(CheckPointer(pUnk));
1053 // First see if the object implements the IAgileObject marker interface
1054 SafeComHolderPreemp<IAgileObject> pAgileObject;
1055 HRESULT hr = SafeQueryInterfacePreemp(pUnk, IID_IAgileObject, (IUnknown**)&pAgileObject);
1056 LogInteropQI(pUnk, IID_IAgileObject, hr, "IUnkEntry::IsComponentFreeThreaded: QI for IAgileObject");
1064 SafeComHolderPreemp<IMarshal> pMarshal = NULL;
1066 // If not, then we can try to determine if the component agregates the FTM via IMarshal.
1067 hr = SafeQueryInterfacePreemp(pUnk, IID_IMarshal, (IUnknown **)&pMarshal);
1068 LogInteropQI(pUnk, IID_IMarshal, hr, "IUnkEntry::IsComponentFreeThreaded: QI for IMarshal");
1073 // The COM component implements IMarshal so we now check to see if the un-marshal class
1074 // for this IMarshal is the FTM's un-marshaler.
1075 hr = pMarshal->GetUnmarshalClass(IID_IUnknown, NULL, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL, &clsid);
1076 if (SUCCEEDED(hr) && clsid == CLSID_InProcFreeMarshaler)
1078 // The un-marshaler is indeed the unmarshaler for the FTM so this object
1079 // is free threaded.
1088 //================================================================
1089 // Helper function to marshal the IUnknown pointer to the stream.
1090 HRESULT IUnkEntry::MarshalIUnknownToStream()
1098 // This must always be called in the right context.
1099 PRECONDITION(m_pCtxCookie == GetCurrentCtxCookie());
1103 IStream *pStream = NULL;
1109 // ensure we register this cookie
1110 IUnknown *pUnk = m_pUnknown;
1111 if (pUnk == (IUnknown *)0xBADF00D)
1113 hr = COR_E_INVALIDCOMOBJECT;
1117 hr = wCoMarshalInterThreadInterfaceInStream(IID_IUnknown, pUnk, &pStream);
1119 if ((hr == REGDB_E_IIDNOTREG) ||
1121 (hr == E_NOINTERFACE) ||
1122 (hr == E_INVALIDARG) ||
1123 (hr == E_UNEXPECTED))
1125 // Interface is not marshallable.
1131 // Try to set the stream in the IUnkEntry. If another thread already set it,
1132 // then we need to release the stream we just set up.
1133 if (FastInterlockCompareExchangePointer(&m_pStream, pStream, NULL) != NULL)
1134 SafeReleaseStream(pStream);
1140 // Method to try to start updating the entry.
1141 bool IUnkEntry::TryUpdateEntry()
1143 WRAPPER_NO_CONTRACT;
1145 CtxEntry *pOldEntry = m_pCtxEntry;
1146 if (((DWORD_PTR)pOldEntry & 1) == 0)
1148 CtxEntry *pNewEntry = (CtxEntry *)((DWORD_PTR)pOldEntry | 1);
1149 return (InterlockedExchangeT(&m_pCtxEntry, pNewEntry) == pOldEntry);
1154 // Method to end updating the entry.
1155 VOID IUnkEntry::EndUpdateEntry()
1157 LIMITED_METHOD_CONTRACT;
1159 CtxEntry *pOldEntry = m_pCtxEntry;
1161 // we should hold the lock
1162 _ASSERTE(((DWORD_PTR)pOldEntry & 1) == 1);
1164 CtxEntry *pNewEntry = (CtxEntry *)((DWORD_PTR)pOldEntry & ~1);
1166 // and it's us who resets the bit
1167 VERIFY(InterlockedExchangeT(&m_pCtxEntry, pNewEntry) == pOldEntry);
1171 #ifdef MDA_SUPPORTED
1173 // Default to a 60 second timeout
1174 #define MDA_CONTEXT_SWITCH_DEADLOCK_TIMEOUT 60000
1175 #define MDA_CONTEXT_SWITCH_DEADLOCK_ITERATION_COUNT 1000
1177 struct MDAContextSwitchDeadlockArgs
1180 LPVOID OriginContext;
1181 LPVOID DestinationContext;
1184 DWORD WINAPI MDAContextSwitchDeadlockThreadProc(LPVOID lpParameter)
1191 PRECONDITION(CheckPointer(MDA_GET_ASSISTANT(ContextSwitchDeadlock)));
1195 // We need to ensure a thread object has been set up since we will toggle to cooperative GC mode
1196 // inside the wait loop.
1197 Thread *pThread = SetupThreadNoThrow();
1198 if (pThread == NULL)
1200 // If we failed to allocate the thread object, we will skip running the watchdog thread
1201 // and simply return.
1206 NewHolder<MDAContextSwitchDeadlockArgs> args = (MDAContextSwitchDeadlockArgs*)lpParameter;
1208 // This interesting piece of code allows us to avoid firing the MDA when a process is
1209 // being debugged while the context transition is in progress. It is needed because
1210 // CLREvent::Wait will timeout after the specified amount of wall time, even if the
1211 // process is broken into the debugger for a portion of the time. By splitting the
1212 // wait into a bunch of smaller waits, we allow many more step/continue operations to
1213 // occur before we signal the timeout.
1214 for (int i = 0; i < MDA_CONTEXT_SWITCH_DEADLOCK_ITERATION_COUNT; i++)
1216 retval = args->hEvent->Wait(MDA_CONTEXT_SWITCH_DEADLOCK_TIMEOUT / MDA_CONTEXT_SWITCH_DEADLOCK_ITERATION_COUNT, FALSE);
1217 if (retval != WAIT_TIMEOUT)
1220 // Transition to cooperative GC mode and back. This will allow us to stop the timeout
1221 // if we are broken in while managed only debugging.
1227 if (retval == WAIT_TIMEOUT)
1229 // We didn't transition into the context within the alloted timeout period.
1230 // We'll fire the mda and close the event, but we can't delete is as the
1231 // thread may still complete the transition and attempt to signal the event.
1232 // So we'll just leak it and let the transition thread recognize that the
1233 // event is no longer valid so it can simply delete it.
1234 MDA_TRIGGER_ASSISTANT(ContextSwitchDeadlock, ReportDeadlock(args->OriginContext, args->DestinationContext));
1236 args->hEvent->CloseEvent();
1240 delete args->hEvent;
1246 void QueueMDAThread(CtxEntryEnterContextCallbackData* data)
1253 PRECONDITION(CheckPointer(data->m_hTimeoutEvent));
1257 MDAContextSwitchDeadlockArgs* args = NULL;
1261 args = new MDAContextSwitchDeadlockArgs;
1263 args->OriginContext = GetCurrentCtxCookie();
1264 args->DestinationContext = data->m_pCtxCookie;
1265 args->hEvent = data->m_hTimeoutEvent;
1267 // Will execute in the Default AppDomain
1268 ThreadpoolMgr::QueueUserWorkItem(MDAContextSwitchDeadlockThreadProc, (LPVOID)args, WT_EXECUTELONGFUNCTION);
1272 delete data->m_hTimeoutEvent;
1273 data->m_hTimeoutEvent = NULL;
1278 EX_END_CATCH(SwallowAllExceptions);
1281 #endif // MDA_SUPPORTED
1284 // Initialize the entry, returns true on success (i.e. the entry was free).
1285 bool InterfaceEntry::Init(MethodTable* pMT, IUnknown* pUnk)
1295 // It is important the fields be set in this order.
1296 if (InterlockedCompareExchangeT(&m_pUnknown, pUnk, NULL) == NULL)
1298 m_pMT = (IE_METHODTABLE_PTR)pMT;
1304 // Helper to determine if the entry is free.
1305 BOOL InterfaceEntry::IsFree()
1307 LIMITED_METHOD_CONTRACT;
1308 return m_pUnknown.Load() == NULL;
1311 void InterfaceEntry::Free()
1313 LIMITED_METHOD_CONTRACT;
1315 // We use the m_pUnknown field to synchronize access to the entry so that's the only
1316 // one we need to reset. After all, the set of interfaces that the object is known to
1317 // support is one of the most important debugging cues so let's keep m_pMT intact.
1318 m_pUnknown.Store(NULL);
1321 //================================================================
1322 // Constructor for the context entry.
1323 CtxEntry::CtxEntry(LPVOID pCtxCookie, Thread *pSTAThread)
1324 : m_pCtxCookie(pCtxCookie)
1327 , m_pSTAThread(pSTAThread)
1329 WRAPPER_NO_CONTRACT;
1332 //================================================================
1333 // Destructor for the context entry.
1334 CtxEntry::~CtxEntry()
1341 PRECONDITION(m_dwRefCount == 0);
1345 // If the context is a valid context then release it.
1346 if (m_pObjCtx && !g_fProcessDetach)
1348 SafeRelease(m_pObjCtx);
1352 // Set the context cookie to 0xBADF00D to indicate the current context
1353 // has been deleted.
1354 m_pCtxCookie = (LPVOID)0xBADF00D;
1357 //================================================================
1358 // Initialization method for the context entry.
1359 VOID CtxEntry::Init()
1366 INJECT_FAULT(COMPlusThrowOM());
1368 // Make sure COM has been started
1369 PRECONDITION(g_fComStarted == TRUE);
1373 // Retrieve the IObjectContext.
1374 HRESULT hr = GetCurrentObjCtx(&m_pObjCtx);
1376 // In case the call to GetCurrentObjCtx fails (which should never really happen)
1377 // we will throw an exception.
1383 // Add a reference to the CtxEntry.
1384 DWORD CtxEntry::AddRef()
1394 ULONG cbRef = FastInterlockIncrement((LONG*)&m_dwRefCount);
1395 LOG((LF_INTEROP, LL_INFO100, "CtxEntry::Addref %8.8x with %d\n", this, cbRef));
1400 //================================================================
1401 // Method to decrement the ref count of the context entry.
1402 DWORD CtxEntry::Release()
1409 PRECONDITION(m_dwRefCount > 0);
1413 LPVOID pCtxCookie = m_pCtxCookie;
1415 LONG cbRef = FastInterlockDecrement((LONG*)&m_dwRefCount);
1416 LOG((LF_INTEROP, LL_INFO100, "CtxEntry::Release %8.8x with %d\n", this, cbRef));
1418 // If the ref count falls to 0, try and delete the ctx entry.
1419 // This might not end up deleting it if another thread tries to
1420 // retrieve this ctx entry at the same time this one tries
1423 CtxEntryCache::GetCtxEntryCache()->TryDeleteCtxEntry(pCtxCookie);
1425 // WARNING: The this pointer cannot be used at this point.
1429 //================================================================
1430 // Method to transition into the context and call the callback
1431 // from within the context.
1432 HRESULT CtxEntry::EnterContext(PFNCTXCALLBACK pCallbackFunc, LPVOID pData)
1440 PRECONDITION(CheckPointer(pCallbackFunc));
1441 PRECONDITION(CheckPointer(pData));
1442 // This should not be called if the this context is the current context.
1443 PRECONDITION(m_pCtxCookie != GetCurrentCtxCookie());
1449 // If we are in process detach, we cannot safely try to enter another context
1450 // since we don't know if OLE32 is still loaded.
1451 if (g_fProcessDetach)
1453 LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08%x has failed since we are in process detach\n", m_pCtxCookie));
1454 return RPC_E_DISCONNECTED;
1457 // Make sure we are in preemptive GC mode before we call out to COM.
1460 // Prepare the information struct passed into the callback.
1461 CtxEntryEnterContextCallbackData CallbackInfo;
1462 CallbackInfo.m_pUserCallbackFunc = pCallbackFunc;
1463 CallbackInfo.m_pUserData = pData;
1464 CallbackInfo.m_pCtxCookie = m_pCtxCookie;
1465 CallbackInfo.m_UserCallbackHR = E_FAIL;
1466 #ifdef MDA_SUPPORTED
1467 CallbackInfo.m_hTimeoutEvent = NULL;
1469 MdaContextSwitchDeadlock* mda = MDA_GET_ASSISTANT(ContextSwitchDeadlock);
1474 CallbackInfo.m_hTimeoutEvent = new CLREvent();
1475 CallbackInfo.m_hTimeoutEvent->CreateAutoEvent(FALSE);
1479 if (CallbackInfo.m_hTimeoutEvent)
1481 delete CallbackInfo.m_hTimeoutEvent;
1482 CallbackInfo.m_hTimeoutEvent = NULL;
1485 EX_END_CATCH(SwallowAllExceptions);
1487 #endif // MDA_SUPPORTED
1489 // Retrieve the IContextCallback interface from the IObjectContext.
1490 SafeComHolderPreemp<IContextCallback> pCallback;
1491 hr = SafeQueryInterfacePreemp(m_pObjCtx, IID_IContextCallback, (IUnknown**)&pCallback);
1492 LogInteropQI(m_pObjCtx, IID_IContextCallback, hr, "QI for IID_IContextCallback");
1493 _ASSERTE(SUCCEEDED(hr) && pCallback);
1495 // Setup the callback data structure with the callback Args
1496 ComCallData callBackData;
1497 callBackData.dwDispid = 0;
1498 callBackData.dwReserved = 0;
1499 callBackData.pUserDefined = &CallbackInfo;
1501 #ifdef MDA_SUPPORTED
1502 // Make sure we don't deadlock when trying to enter the context.
1503 if (mda && CallbackInfo.m_hTimeoutEvent)
1505 QueueMDAThread(&CallbackInfo);
1511 hr = ((IContextCallback*)pCallback)->ContextCallback(EnterContextCallback, &callBackData, IID_IEnterActivityWithNoLock, 2, NULL);
1515 hr = GET_EXCEPTION()->GetHR();
1517 EX_END_CATCH(SwallowAllExceptions);
1521 // If the transition failed because of an aborted func eval, simply propagate
1522 // the HRESULT/IErrorInfo back to the caller as we cannot throw here.
1523 SafeComHolder<IErrorInfo> pErrorInfo = CheckForFuncEvalAbortNoThrow(hr);
1524 if (pErrorInfo != NULL)
1526 LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08X has failed since the debugger is blocking it\n", m_pCtxCookie));
1528 // put the IErrorInfo back
1529 SetErrorInfo(0, pErrorInfo);
1533 // The context is disconnected so we cannot transition into it.
1534 LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08X has failed since the context has disconnected\n", m_pCtxCookie));
1542 //================================================================
1543 // Callback function called by DoCallback.
1544 HRESULT __stdcall CtxEntry::EnterContextCallback(ComCallData* pComCallData)
1552 PRECONDITION(CheckPointer(pComCallData));
1556 // Retrieve the callback data.
1557 CtxEntryEnterContextCallbackData *pData = (CtxEntryEnterContextCallbackData*)pComCallData->pUserDefined;
1560 #ifdef MDA_SUPPORTED
1561 // If active, signal the MDA watcher so we don't accidentally trigger a timeout.
1562 MdaContextSwitchDeadlock* mda = MDA_GET_ASSISTANT(ContextSwitchDeadlock);
1565 // If our watchdog worker is still waiting on us, the event will be valid.
1566 if (pData->m_hTimeoutEvent->IsValid())
1568 pData->m_hTimeoutEvent->Set();
1572 // If we did timeout, we will have already cleaned up the event...just delete it now.
1573 delete pData->m_hTimeoutEvent;
1576 #endif // MDA_SUPPORTED
1578 Thread *pThread = GetThread();
1580 // Make sure the thread has been set before we call the user callback function.
1583 // huh! we are in the middle of shutdown
1584 // and there is no way we can add a new thread
1585 // so let us just return RPC_E_DISCONNECTED
1586 // look at the pCallBack->DoCallback above
1587 // to see why we are returning this SCODE
1589 return RPC_E_DISCONNECTED;
1591 // Otherwise, we need to create a managed thread object for this new thread
1595 pThread = SetupThreadNoThrow(&hr);
1596 if (pThread == NULL)
1601 // at this point we should be in the right context on NT4,
1602 // if not then it is possible that the actual apartment state for this
1603 // thread has changed and we have stale info in our thread or the CtxEntry
1604 LPVOID pCtxCookie = GetCurrentCtxCookie();
1605 _ASSERTE(pCtxCookie);
1606 if (pData->m_pCtxCookie != pCtxCookie)
1607 return RPC_E_DISCONNECTED;
1609 // Call the user callback function and store the return value the
1611 pData->m_UserCallbackHR = pData->m_pUserCallbackFunc(pData->m_pUserData);
1613 // Return S_OK to indicate the context transition was successfull.