1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-08 03:27:04 +09:00
Satori/src/coreclr/vm/comcallablewrapper.cpp
2022-07-10 04:03:08 -07:00

5016 lines
165 KiB
C++

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//--------------------------------------------------------------------------
// ComCallablewrapper.cpp
//
// Implementation for various Wrapper classes
//
// COMWrapper : COM callable wrappers for CLR interfaces
//
//--------------------------------------------------------------------------
#include "common.h"
#include "clrtypes.h"
#include "comcallablewrapper.h"
#include "object.h"
#include "field.h"
#include "method.hpp"
#include "class.h"
#include "runtimecallablewrapper.h"
#include "olevariant.h"
#include "cachelinealloc.h"
#include "threads.h"
#include "ceemain.h"
#include "excep.h"
#include "stublink.h"
#include "cgensys.h"
#include "comtoclrcall.h"
#include "clrtocomcall.h"
#include "comutilnative.h"
#include "eeconfig.h"
#include "interoputil.h"
#include "dispex.h"
#include "guidfromname.h"
#include "comconnectionpoints.h"
#include <objsafe.h> // IID_IObjctSafe
#include "virtualcallstub.h"
#include "contractimpl.h"
#include "caparser.h"
#include "appdomain.inl"
#include "typestring.h"
// The enum that describes the value of the IDispatchImplAttribute custom attribute.
enum IDispatchImplType
{
SystemDefinedImpl = 0,
InternalImpl = 1,
CompatibleImpl = 2
};
// The enum that describe the value of System.Runtime.InteropServices.CustomQueryInterfaceResult
// It is the return value of the method System.Runtime.InteropServices.ICustomQueryInterface.GetInterface
enum CustomQueryInterfaceResult
{
Handled = 0,
NotHandled = 1,
Failed = 2
};
typedef CQuickArray<MethodTable*> CQuickEEClassPtrs;
// Startup and shutdown lock
CrstStatic g_CreateWrapperTemplateCrst;
// This is the prestub that is used for Com calls entering COM+
extern "C" VOID ComCallPreStub();
class NewCCWHolderBase : public HolderBase<ComCallWrapper *>
{
protected:
NewCCWHolderBase(ComCallWrapper *pValue)
: HolderBase<ComCallWrapper *>(pValue)
{
}
// BaseHolder only initialize BASE with one parameter, so I had to
// use a separate function to set the cache which will be used in the release
void SetCache(ComCallWrapperCache *pCache)
{
m_pCache = pCache;
}
void DoAcquire()
{
// Do nothing
}
void DoRelease()
{
this->m_value->FreeWrapper(m_pCache);
}
private :
ComCallWrapperCache *m_pCache;
};
typedef ComCallWrapper *ComCallWrapperPtr;
// This is used to hold a newly created CCW. It will release the CCW (and linked wrappers)
// upon exit, if SuppressRelease() is not called. It doesn't try to release the SimpleComCallWrapper
// or destroy the handle
// I need to use BaseHolder instead of BaseWrapper because BaseHolder allows me to use a class as BASE
//
class NewCCWHolder : public BaseHolder<ComCallWrapperPtr, NewCCWHolderBase>
{
public :
NewCCWHolder(ComCallWrapperCache *pCache)
{
SetCache(pCache);
}
ComCallWrapperPtr& operator=(ComCallWrapperPtr p)
{
Assign(p);
return m_value;
}
FORCEINLINE const ComCallWrapperPtr &operator->()
{
return this->m_value;
}
operator ComCallWrapperPtr()
{
return m_value;
}
};
// Calls Destruct on ComCallMethodDesc's in an array - used as backout code when laying out ComMethodTable.
void DestructComCallMethodDescs(ArrayList *pDescArray)
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
}
CONTRACTL_END;
ArrayList::Iterator i = pDescArray->Iterate();
while (i.Next())
{
ComCallMethodDesc *pCMD = (ComCallMethodDesc *)i.GetElement();
pCMD->Destruct();
}
}
typedef Wrapper<ArrayList *, DoNothing<ArrayList *>, DestructComCallMethodDescs> ComCallMethodDescArrayHolder;
// Forward declarations
static bool GetComIPFromCCW_HandleCustomQI(ComCallWrapper *pWrap, REFIID riid, MethodTable * pIntfMT, IUnknown **ppUnkOut);
//--------------------------------------------------------------------------
// IsDuplicateClassItfMD(MethodDesc *pMD, unsigned int ix)
// Determines if the specified method desc is a duplicate.
// Note that this method should only be called to determine duplicates on
// the class interface.
//--------------------------------------------------------------------------
bool IsDuplicateClassItfMD(MethodDesc *pMD, unsigned int ix)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
if (!pMD->IsDuplicate())
return false;
if (pMD->GetSlot() == ix)
return false;
return true;
}
//--------------------------------------------------------------------------
// IsDuplicateClassItfMD(MethodDesc *pMD, unsigned int ix)
// Determines if the specified method desc is a duplicate.
// Note that this method should only be called to determine duplicates on
// the class interface.
//--------------------------------------------------------------------------
bool IsDuplicateClassItfMD(InteropMethodTableSlotData *pInteropMD, unsigned int ix)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pInteropMD));
}
CONTRACTL_END;
if (!pInteropMD->IsDuplicate())
return false;
if (pInteropMD->GetSlot() == ix)
return false;
return true;
}
bool IsOverloadedComVisibleMember(MethodDesc *pMD, MethodDesc *pParentMD)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pMD));
PRECONDITION(CheckPointer(pParentMD));
}
CONTRACTL_END;
// Array methods should never be exposed to COM.
if (pMD->IsArray())
return FALSE;
// If this is the same MethodDesc, then it is not an overload at all
if (pMD == pParentMD)
return FALSE;
// If the new member is not visible from COM then it isn't an overloaded public member.
if (!IsMethodVisibleFromCom(pMD))
return FALSE;
// If the old member is visible from COM then the new one is not a public overload.
if (IsMethodVisibleFromCom(pParentMD))
return FALSE;
// The new member is a COM visible overload of a non COM visible member.
return TRUE;
}
bool IsNewComVisibleMember(MethodDesc *pMD)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
// Array methods should never be exposed to COM.
if (pMD->IsArray())
return FALSE;
// Check to see if the member is visible from COM.
return IsMethodVisibleFromCom(pMD) ? true : false;
}
bool IsStrictlyUnboxed(MethodDesc *pMD)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
MethodTable *pMT = pMD->GetMethodTable();
MethodTable::MethodIterator it(pMT);
for (; it.IsValid(); it.Next()) {
MethodDesc *pCurrMD = it.GetMethodDesc();
if (pCurrMD->GetMemberDef() == pMD->GetMemberDef())
return false;
}
return true;
}
void FillInComVtableSlot(SLOT* pComVtable, // must point to the first slot after the "extra slots" (e.g. IUnknown/IDispatch slots)
UINT uComSlot, // must be relative to pComVtable
ComCallMethodDesc* pMD)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pComVtable));
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
pComVtable[uComSlot] = (SLOT)(((BYTE*)pMD - COMMETHOD_CALL_PRESTUB_SIZE)ARM_ONLY(+THUMB_CODE));
}
ComCallMethodDesc* ComMethodTable::ComCallMethodDescFromSlot(unsigned i)
{
CONTRACT(ComCallMethodDesc*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACT_END;
ComCallMethodDesc* pCMD = NULL;
SLOT* rgVtable = (SLOT*)((ComMethodTable *)this+1);
// NOTE: make sure to keep this in sync with FillInComVtableSlot
pCMD = (ComCallMethodDesc*)(((BYTE *)rgVtable[i]) + COMMETHOD_CALL_PRESTUB_SIZE ARM_ONLY(-THUMB_CODE));
RETURN pCMD;
}
//--------------------------------------------------------------------------
// Determines if the Compatible IDispatch implementation is required for
// the specified class.
//--------------------------------------------------------------------------
bool IsOleAutDispImplRequiredForClass(MethodTable *pClass)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pClass));
}
CONTRACTL_END;
HRESULT hr;
const BYTE * pVal;
ULONG cbVal;
Assembly * pAssembly = pClass->GetAssembly();
IDispatchImplType DispImplType = SystemDefinedImpl;
// First check for the IDispatchImplType custom attribute first.
hr = pClass->GetCustomAttribute(WellKnownAttribute::IDispatchImpl, (const void**)&pVal, &cbVal);
if (hr == S_OK)
{
CustomAttributeParser cap(pVal, cbVal);
IfFailThrow(cap.SkipProlog());
UINT8 u1;
IfFailThrow(cap.GetU1(&u1));
DispImplType = (IDispatchImplType)u1;
if ((DispImplType > 2) || (DispImplType < 0))
DispImplType = SystemDefinedImpl;
}
// If the custom attribute was set to something other than system defined then we will use that.
if (DispImplType != SystemDefinedImpl)
return (bool) (DispImplType == CompatibleImpl);
// Check to see if the assembly has the IDispatchImplType attribute set.
hr = pAssembly->GetCustomAttribute(pAssembly->GetManifestToken(), WellKnownAttribute::IDispatchImpl, (const void**)&pVal, &cbVal);
if (hr == S_OK)
{
CustomAttributeParser cap(pVal, cbVal);
IfFailThrow(cap.SkipProlog());
UINT8 u1;
IfFailThrow(cap.GetU1(&u1));
DispImplType = (IDispatchImplType)u1;
if ((DispImplType > 2) || (DispImplType < 0))
DispImplType = SystemDefinedImpl;
}
// If the custom attribute was set to something other than system defined then we will use that.
if (DispImplType != SystemDefinedImpl)
return (bool) (DispImplType == CompatibleImpl);
// Removed registry key check per reg cleanup bug 45978
// Effect: Will return false so code cleanup
return false;
}
//--------------------------------------------------------------------------
// This routine is called anytime a com method is invoked for the first time.
// It is responsible for generating the real stub.
//
// This function's only caller is the ComPreStub.
//
// For the duration of the prestub, the current Frame on the stack
// will be a PrestubMethodFrame (which derives from FramedMethodFrame.)
// Hence, things such as exceptions and gc will work normally.
//
// On rare occasions, the ComPrestub may get called twice because two
// threads try to call the same method simultaneously.
//--------------------------------------------------------------------------
extern "C" PCODE ComPreStubWorker(ComPrestubMethodFrame *pPFrame, UINT64 *pErrorReturn)
{
CONTRACT (PCODE)
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
ENTRY_POINT;
PRECONDITION(CheckPointer(pPFrame));
PRECONDITION(CheckPointer(pErrorReturn));
}
CONTRACT_END;
HRESULT hr = S_OK;
PCODE retAddr = NULL;
BEGIN_ENTRYPOINT_VOIDRET;
PCODE pStub = NULL;
BOOL fNonTransientExceptionThrown = FALSE;
ComCallMethodDesc *pCMD = pPFrame->GetComCallMethodDesc();
IUnknown *pUnk = *(IUnknown **)pPFrame->GetPointerToArguments();
OBJECTREF pThrowable = NULL;
Thread* pThread = SetupThreadNoThrow();
if (pThread == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
if (pThread->PreemptiveGCDisabled())
{
EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(
COR_E_EXECUTIONENGINE,
W("Invalid Program: attempted to call a COM method from managed code."));
}
// Transition to cooperative GC mode before we start setting up the stub.
GCX_COOP();
// The PreStub allocates memory for the frame, but doesn't link it
// into the chain or fully initialize it. Do so now.
pPFrame->Init();
pPFrame->Push();
ComCallWrapper *pWrap = NULL;
GCPROTECT_BEGIN(pThrowable)
{
// We need a try/catch around the code to enter the domain since entering
// an AppDomain can throw an exception.
EX_TRY
{
// check for invalid wrappers in the debug build
// in the retail all bets are off
pWrap = ComCallWrapper::GetWrapperFromIP(pUnk);
_ASSERTE(pWrap->IsWrapperActive() || pWrap->IsAggregated());
// Make sure we're not trying to call on the class interface of a class with ComVisible(false) members
// in its hierarchy.
if ((pCMD->IsFieldCall()) || (NULL == pCMD->GetInterfaceMethodDesc() && !pCMD->GetMethodDesc()->IsInterface()))
{
// If we have a fieldcall or a null interface MD, we could be dealing with the IClassX interface.
ComMethodTable* pComMT = ComMethodTable::ComMethodTableFromIP(pUnk);
pComMT->CheckParentComVisibility(FALSE);
}
{
OBJECTREF pADThrowable = NULL;
BOOL fExceptionThrown = FALSE;
GCPROTECT_BEGIN(pADThrowable);
{
if (pCMD->IsMethodCall())
{
// We need to ensure all valuetypes are loaded in
// the target domain so that GC can happen later
EX_TRY
{
MethodDesc* pTargetMD = pCMD->GetMethodDesc();
MetaSig::EnsureSigValueTypesLoaded(pTargetMD);
}
EX_CATCH
{
pADThrowable = GET_THROWABLE();
}
EX_END_CATCH(RethrowTerminalExceptions);
}
if (pADThrowable != NULL)
{
// Transform the exception into an HRESULT. This also sets up
// an IErrorInfo on the current thread for the exception.
hr = SetupErrorInfo(pADThrowable);
pADThrowable = NULL;
fExceptionThrown = TRUE;
}
}
GCPROTECT_END();
if(!fExceptionThrown)
{
GCPROTECT_BEGIN(pADThrowable);
{
// We need a try/catch around the call to the worker since we need
// to transform any exceptions into HRESULTs. We want to do this
// inside the AppDomain of the CCW.
EX_TRY
{
GCX_PREEMP();
pStub = ComCall::GetComCallMethodStub(pCMD);
}
EX_CATCH
{
fNonTransientExceptionThrown = !GET_EXCEPTION()->IsTransient();
pADThrowable = GET_THROWABLE();
}
EX_END_CATCH(RethrowTerminalExceptions);
if (pADThrowable != NULL)
{
// Transform the exception into an HRESULT. This also sets up
// an IErrorInfo on the current thread for the exception.
hr = SetupErrorInfo(pADThrowable);
pADThrowable = NULL;
}
}
GCPROTECT_END();
}
}
}
EX_CATCH
{
pThrowable = GET_THROWABLE();
// If an exception was thrown while transitionning back to the original
// AppDomain then can't use the stub and must report an error.
pStub = NULL;
}
EX_END_CATCH(SwallowAllExceptions);
if (pThrowable != NULL)
{
// Transform the exception into an HRESULT. This also sets up
// an IErrorInfo on the current thread for the exception.
hr = SetupErrorInfo(pThrowable);
pThrowable = NULL;
}
}
GCPROTECT_END();
// Unlink the PrestubMethodFrame.
pPFrame->Pop();
if (pStub)
{
// Now, replace the prestub with the new stub.
static_assert((COMMETHOD_CALL_PRESTUB_SIZE - COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET) % DATA_ALIGNMENT == 0,
"The call target in COM prestub must be aligned so we can guarantee atomicity of updates");
UINT_PTR* ppofs = (UINT_PTR*) (((BYTE*)pCMD) - COMMETHOD_CALL_PRESTUB_SIZE + COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET);
ExecutableWriterHolder<UINT_PTR> ppofsWriterHolder(ppofs, sizeof(UINT_PTR));
#ifdef TARGET_X86
*ppofsWriterHolder.GetRW() = ((UINT_PTR)pStub - (size_t)pCMD);
#else
*ppofsWriterHolder.GetRW() = ((UINT_PTR)pStub);
#endif
// Return the address of the prepad. The prepad will regenerate the hidden parameter and due
// to the update above will execute the new stub code the second time around.
retAddr = (PCODE)(((BYTE*)pCMD - COMMETHOD_CALL_PRESTUB_SIZE)ARM_ONLY(+THUMB_CODE));
goto Exit;
}
}
// We failed to set up the stub so we need to report an error to the caller.
//
// IMPORTANT: No floating point operations can occur after this point!
//
*pErrorReturn = 0;
if (pCMD->IsNativeHResultRetVal())
*pErrorReturn = hr;
else if (pCMD->IsNativeBoolRetVal())
*pErrorReturn = 0;
else if (pCMD->IsNativeR4RetVal())
setFPReturn(4, CLR_NAN_32);
else if (pCMD->IsNativeR8RetVal())
setFPReturn(8, CLR_NAN_64);
else
_ASSERTE(pCMD->IsNativeVoidRetVal());
#ifdef TARGET_X86
// Number of bytes to pop is upper half of the return value on x86
*(((INT32 *)pErrorReturn) + 1) = pCMD->GetNumStackBytes();
#endif
retAddr = NULL;
Exit:
END_ENTRYPOINT_VOIDRET;
RETURN retAddr;
}
FORCEINLINE void CPListRelease(CQuickArray<ConnectionPoint*>* value)
{
WRAPPER_NO_CONTRACT;
if (value)
{
// Delete all the connection points.
for (UINT i = 0; i < value->Size(); i++)
delete (*value)[i];
// Delete the list itself.
delete value;
}
}
typedef CQuickArray<ConnectionPoint*> CPArray;
FORCEINLINE void CPListDoNothing(CPArray*)
{
LIMITED_METHOD_CONTRACT;
}
class CPListHolder : public Wrapper<CPArray*, CPListDoNothing, CPListRelease, NULL>
{
public:
CPListHolder(CPArray* p = NULL)
: Wrapper<CPArray*, CPListDoNothing, CPListRelease, NULL>(p)
{
WRAPPER_NO_CONTRACT;
}
FORCEINLINE void operator=(CPArray* p)
{
WRAPPER_NO_CONTRACT;
Wrapper<CPArray*, CPListDoNothing, CPListRelease, NULL>::operator=(p);
}
};
NOINLINE void LogCCWRefCountChange_BREAKPOINT(ComCallWrapper *pCCW)
{
LIMITED_METHOD_CONTRACT;
// Empty function to put breakpoint on when debugging CCW ref-counting issues.
// At this point *(pCCW->m_ppThis) is the managed object wrapped by the CCW.
// Bogus code so the function is not optimized away.
if (pCCW == NULL)
DebugBreak();
}
void SimpleComCallWrapper::BuildRefCountLogMessage(LPCSTR szOperation, StackSString &ssMessage, ULONG dwEstimatedRefCount)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// Don't worry about domain unloading in CoreCLR
LPCUTF8 pszClassName;
LPCUTF8 pszNamespace;
if (SUCCEEDED(m_pMT->GetMDImport()->GetNameOfTypeDef(m_pMT->GetCl(), &pszClassName, &pszNamespace)))
{
OBJECTHANDLE handle = GetMainWrapper()->GetObjectHandle();
_UNCHECKED_OBJECTREF obj = NULL;
// Force retrieving the handle without using OBJECTREF and under cooperative mode
// We only need the value in ETW events and it doesn't matter if it is super accurate
if (handle != NULL)
obj = *((_UNCHECKED_OBJECTREF *)(handle));
if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context, CCWRefCountChange))
{
EX_TRY
{
SString className;
className.SetUTF8(pszClassName);
SString nameSpace;
nameSpace.SetUTF8(pszNamespace);
SString operation;
nameSpace.SetUTF8(szOperation);
FireEtwCCWRefCountChange(
handle,
(Object *)obj,
this,
dwEstimatedRefCount,
NULL, // domain value is not interesting in CoreCLR
className.GetUnicode(), nameSpace.GetUnicode(), operation.GetUnicode(), GetClrInstanceId());
}
EX_CATCH
{ }
EX_END_CATCH(SwallowAllExceptions);
}
if (g_pConfig->ShouldLogCCWRefCountChange(pszClassName, pszNamespace))
{
EX_TRY
{
ssMessage.Printf("LogCCWRefCountChange[%s]: '%s.%s', Object=poi(%p)",
szOperation,
pszNamespace,
pszClassName,
handle);
}
EX_CATCH
{ }
EX_END_CATCH(SwallowAllExceptions);
}
}
}
// static
void SimpleComCallWrapper::LogRefCount(ComCallWrapper *pWrap, StackSString &ssMessage, ULONG dwRefCountToLog)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
if (!ssMessage.IsEmpty())
{
EX_TRY
{
ssMessage.AppendPrintf(", RefCount=%u\n", dwRefCountToLog);
OutputDebugStringUtf8(ssMessage.GetUTF8());
}
EX_CATCH
{ }
EX_END_CATCH(SwallowAllExceptions);
LogCCWRefCountChange_BREAKPOINT(pWrap);
}
}
LONGLONG SimpleComCallWrapper::ReleaseImplWithLogging(LONGLONG * pRefCount)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
LONGLONG newRefCount;
StackSString ssMessage;
ComCallWrapper *pWrap = GetMainWrapper();
BuildRefCountLogMessage("Release", ssMessage, GET_EXT_COM_REF(READ_REF(*pRefCount)-1));
// Decrement the ref count
newRefCount = ::InterlockedDecrement64(pRefCount);
LogRefCount(pWrap, ssMessage, GET_EXT_COM_REF(newRefCount));
return newRefCount;
}
//--------------------------------------------------------------------------
// simple ComCallWrapper for all simple std interfaces, that are not used very often
// like IProvideClassInfo, ISupportsErrorInfo etc.
//--------------------------------------------------------------------------
SimpleComCallWrapper::SimpleComCallWrapper()
{
WRAPPER_NO_CONTRACT;
memset(this, 0, sizeof(SimpleComCallWrapper));
}
//--------------------------------------------------------------------------
// VOID SimpleComCallWrapper::Cleanup()
//--------------------------------------------------------------------------
VOID SimpleComCallWrapper::Cleanup()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
// in case the caller stills holds on to the IP
for (int i = 0; i < enum_LastStdVtable; i++)
{
m_rgpVtable[i] = 0;
}
m_pWrap = NULL;
m_pMT = NULL;
if (m_pCPList)
{
for (UINT i = 0; i < m_pCPList->Size(); i++)
delete (*m_pCPList)[i];
delete m_pCPList;
m_pCPList = NULL;
}
if (m_pTemplate)
{
m_pTemplate->Release();
m_pTemplate = NULL;
}
if (m_pAuxData)
{
delete m_pAuxData;
m_pAuxData = NULL;
}
}
VOID SimpleComCallWrapper::Neuter()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(m_pSyncBlock));
PRECONDITION(!IsNeutered());
}
CONTRACTL_END;
STRESS_LOG1 (LF_INTEROP, LL_INFO100, "Neutering CCW 0x%p\n", this->GetMainWrapper());
// Disconnect the object from the CCW
// Starting now, if this object gets passed out
// to unmanaged code, it will create a new CCW tied
// to the domain it was passed out from.
InteropSyncBlockInfo* pInteropInfo = m_pSyncBlock->GetInteropInfoNoCreate();
if (pInteropInfo)
pInteropInfo->SetCCW(NULL);
// NULL the syncblock entry - we can't hang onto this anymore as the syncblock will be killed asynchronously to us.
ResetSyncBlock();
// Disconnect the CCW from the object
// Calls made on this CCW will no longer succeed.
// The CCW has been neutered.
// do this for each of the CCWs
m_pWrap->Neuter();
StackSString ssMessage;
ComCallWrapper *pWrap = m_pWrap;
if (g_pConfig->LogCCWRefCountChangeEnabled())
{
BuildRefCountLogMessage("Neuter", ssMessage, GET_EXT_COM_REF(READ_REF(m_llRefCount) | CLEANUP_SENTINEL));
}
// Set the neutered bit on the ref-count.
LONGLONG *pRefCount = &m_llRefCount;
LONGLONG oldRefCount = *pRefCount;
LONGLONG newRefCount = oldRefCount | CLEANUP_SENTINEL;
while (InterlockedCompareExchange64((LONGLONG*)pRefCount, newRefCount, oldRefCount) != oldRefCount)
{
oldRefCount = *pRefCount;
newRefCount = oldRefCount | CLEANUP_SENTINEL;
}
// IMPORTANT: Do not touch instance fields or any other data associated with the CCW beyond this
// point unless newRefCount equals CLEANUP_SENTINEL (it's the only case when we know that Release
// could not swoop in and destroy our data structures).
if (g_pConfig->LogCCWRefCountChangeEnabled())
{
LogRefCount(pWrap, ssMessage, GET_EXT_COM_REF(newRefCount));
}
// If we hit the sentinel value, it's our responsibility to clean up.
if (newRefCount == CLEANUP_SENTINEL)
m_pWrap->Cleanup();
}
//--------------------------------------------------------------------------
//destructor
//--------------------------------------------------------------------------
SimpleComCallWrapper::~SimpleComCallWrapper()
{
WRAPPER_NO_CONTRACT;
Cleanup();
}
//--------------------------------------------------------------------------
// Init, with the MethodTable, pointer to the vtable of the interface
// and the main ComCallWrapper if the interface needs it
//--------------------------------------------------------------------------
void SimpleComCallWrapper::InitNew(OBJECTREF oref, ComCallWrapperCache *pWrapperCache, ComCallWrapper* pWrap,
SyncBlock *pSyncBlock,
ComCallWrapperTemplate* pTemplate)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
PRECONDITION(oref != NULL);
PRECONDITION(CheckPointer(pWrap));
PRECONDITION(CheckPointer(pWrapperCache, NULL_OK));
PRECONDITION(CheckPointer(pSyncBlock, NULL_OK));
PRECONDITION(CheckPointer(pTemplate));
PRECONDITION(m_pSyncBlock == NULL);
PRECONDITION(CheckPointer(g_pExceptionClass));
}
CONTRACTL_END;
MethodTable* pMT = pTemplate->GetClassType().GetMethodTable();
PREFIX_ASSUME(pMT != NULL);
m_pMT = pMT;
m_pWrap = pWrap;
m_pWrapperCache = pWrapperCache;
m_pTemplate = pTemplate;
m_pTemplate->AddRef();
m_pOuter = NULL;
m_pSyncBlock = pSyncBlock;
if (pMT->IsComObjectType())
m_flags |= enum_IsExtendsCom;
#ifdef _DEBUG
for (int i = 0; i < enum_LastStdVtable; i++)
_ASSERTE(GetStdInterfaceKind((IUnknown*)(&g_rgStdVtables[i])) == i);
#endif // _DEBUG
for (int i = 0; i < enum_LastStdVtable; i++)
m_rgpVtable[i] = g_rgStdVtables[i];
// If the managed object extends a COM base class then we need to set IProvideClassInfo
// to NULL until we determine if we need to use the IProvideClassInfo of the base class
// or the one of the managed class.
if (IsExtendsCOMObject())
m_rgpVtable[enum_IProvideClassInfo] = NULL;
// IErrorInfo is valid only for exception classes
m_rgpVtable[enum_IErrorInfo] = NULL;
// IDispatchEx is valid only for classes that have IExpando capabilities - which is no longer supported.
m_rgpVtable[enum_IDispatchEx] = NULL;
}
//--------------------------------------------------------------------------
// ReInit,with the new sync block and the urt context
//--------------------------------------------------------------------------
void SimpleComCallWrapper::ReInit(SyncBlock* pSyncBlock)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pSyncBlock));
}
CONTRACTL_END;
m_pSyncBlock = pSyncBlock;
}
//--------------------------------------------------------------------------
// Returns TRUE if the ICustomQI implementation returns Handled or Failed for IID_IMarshal.
//--------------------------------------------------------------------------
BOOL SimpleComCallWrapper::CustomQIRespondsToIMarshal()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(GetComCallWrapperTemplate()->SupportsICustomQueryInterface());
}
CONTRACTL_END;
if ((m_flags & enum_CustomQIRespondsToIMarshal_Inited) == 0)
{
DWORD newFlags = enum_CustomQIRespondsToIMarshal_Inited;
SafeComHolder<IUnknown> pUnk;
if (GetComIPFromCCW_HandleCustomQI(GetMainWrapper(), IID_IMarshal, NULL, &pUnk))
{
newFlags |= enum_CustomQIRespondsToIMarshal;
}
InterlockedOr((LONG*)&m_flags, newFlags);
}
return (m_flags & enum_CustomQIRespondsToIMarshal);
}
//--------------------------------------------------------------------------
// Initializes the information used for exposing exceptions to COM.
//--------------------------------------------------------------------------
void SimpleComCallWrapper::InitExceptionInfo()
{
LIMITED_METHOD_CONTRACT;
m_rgpVtable[enum_IErrorInfo] = g_rgStdVtables[enum_IErrorInfo];
}
//--------------------------------------------------------------------------
// Initializes the IDispatchEx information.
//--------------------------------------------------------------------------
void SimpleComCallWrapper::InitDispatchExInfo()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
// Make sure the class supports at least IReflect..
PRECONDITION(SupportsIReflect(m_pMT));
}
CONTRACTL_END;
SimpleCCWAuxData *pAuxData = GetOrCreateAuxData();
if (pAuxData->m_pDispatchExInfo)
return;
// Create the DispatchExInfo object.
NewHolder<DispatchExInfo> pDispExInfo = new DispatchExInfo(this, m_pMT);
// Synchronize the DispatchExInfo with the actual object.
pDispExInfo->SynchWithManagedView();
// Swap the lock into the class member in a thread safe manner.
if (NULL == InterlockedCompareExchangeT(&pAuxData->m_pDispatchExInfo, pDispExInfo.GetValue(), NULL))
pDispExInfo.SuppressRelease();
// Set the vtable entry to ensure that the next QI call will return immediately.
m_rgpVtable[enum_IDispatchEx] = g_rgStdVtables[enum_IDispatchEx];
}
void SimpleComCallWrapper::SetUpCPList()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
CQuickArray<MethodTable *> SrcItfList;
// If the list has already been set up, then return.
if (m_pCPList)
return;
// Retrieve the list of COM source interfaces for the managed class.
GetComSourceInterfacesForClass(m_pMT, SrcItfList);
// Call the helper to do the rest of the set up.
SetUpCPListHelper(SrcItfList.Ptr(), (int)SrcItfList.Size());
}
void SimpleComCallWrapper::SetUpCPListHelper(MethodTable **apSrcItfMTs, int cSrcItfs)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(apSrcItfMTs));
}
CONTRACTL_END;
CPListHolder pCPList = NULL;
ComCallWrapper *pWrap = GetMainWrapper();
int NumCPs = 0;
// Allocate the list of connection points.
pCPList = CreateCPArray();
pCPList->AllocThrows(cSrcItfs);
for (int i = 0; i < cSrcItfs; i++)
{
// Create a CP helper thru which CP operations will be done.
// Should we throw here instead of ignoring creation errors?
ConnectionPoint *pCP = TryCreateConnectionPoint(pWrap, apSrcItfMTs[i]);
if (pCP != NULL)
{
// Add the connection point to the list.
(*pCPList)[NumCPs++] = pCP;
}
}
// Now that we now the actual number of connection points we were
// able to hook up, resize the array.
pCPList->Shrink(NumCPs);
// Finally, we set the connection point list in the simple wrapper. If
// no other thread already set it, we set pCPList to NULL to indicate
// that ownership has been transfered to the simple wrapper.
if (InterlockedCompareExchangeT(&m_pCPList, pCPList.GetValue(), NULL) == NULL)
pCPList.SuppressRelease();
}
ConnectionPoint *SimpleComCallWrapper::TryCreateConnectionPoint(ComCallWrapper *pWrap, MethodTable *pEventMT)
{
CONTRACT (ConnectionPoint*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pWrap));
PRECONDITION(CheckPointer(pEventMT));
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
ConnectionPoint *pCP = NULL;
EX_TRY
{
pCP = CreateConnectionPoint(pWrap, pEventMT);
}
EX_CATCH
{
pCP = NULL;
}
EX_END_CATCH(RethrowTerminalExceptions)
RETURN pCP;
}
ConnectionPoint *SimpleComCallWrapper::CreateConnectionPoint(ComCallWrapper *pWrap, MethodTable *pEventMT)
{
CONTRACT (ConnectionPoint*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(pWrap));
PRECONDITION(CheckPointer(pEventMT));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
RETURN (new ConnectionPoint(pWrap, pEventMT));
}
CQuickArray<ConnectionPoint*> *SimpleComCallWrapper::CreateCPArray()
{
CONTRACT (CQuickArray<ConnectionPoint*>*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
RETURN (new CQuickArray<ConnectionPoint*>());
}
//--------------------------------------------------------------------------
// Returns TRUE if the simple wrapper represents a COM+ exception object.
//--------------------------------------------------------------------------
BOOL SimpleComCallWrapper::SupportsExceptions(MethodTable *pClass)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pClass, NULL_OK));
}
CONTRACTL_END;
while (pClass != NULL)
{
if (pClass == g_pExceptionClass)
return TRUE;
pClass = pClass->GetComPlusParentMethodTable();
}
return FALSE;
}
//--------------------------------------------------------------------------
// Returns TRUE if the COM+ object that this wrapper represents implements
// IReflect.
//--------------------------------------------------------------------------
BOOL SimpleComCallWrapper::SupportsIReflect(MethodTable *pClass)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pClass));
}
CONTRACTL_END;
// We want to disallow passing out IDispatchEx for Type inheritors to close a security hole.
if (pClass == g_pRuntimeTypeClass)
return FALSE;
if (CoreLibBinder::IsClass(pClass, CLASS__TYPE_BUILDER))
return FALSE;
if (CoreLibBinder::IsClass(pClass, CLASS__TYPE))
return FALSE;
if (CoreLibBinder::IsClass(pClass, CLASS__ENUM_BUILDER))
return FALSE;
// Check to see if the MethodTable associated with the wrapper implements IReflect.
return pClass->ImplementsInterface(CoreLibBinder::GetClass(CLASS__IREFLECT));
}
// NOINLINE to prevent RCWHolder from forcing caller to push/pop an FS:0 handler
NOINLINE BOOL SimpleComCallWrapper::ShouldUseManagedIProvideClassInfo()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
BOOL bUseManagedIProvideClassInfo = TRUE;
// Retrieve the MethodTable of the wrapper.
ComCallWrapper *pMainWrap = GetMainWrapper();
// Only extensible RCW's should go down this code path.
_ASSERTE(pMainWrap->IsExtendsCOMObject());
MethodTable * pObjectMT = pMainWrap->GetSimpleWrapper()->GetMethodTable();
MethodTable * pMT = pObjectMT;
// Find the first COM visible IClassX starting at the bottom of the hierarchy and
// going up the inheritance chain.
while (pMT != NULL)
{
if (IsTypeVisibleFromCom(TypeHandle(pMT)))
break;
pMT = pMT->GetComPlusParentMethodTable();
}
// Since this is an extensible RCW if the CLR classes that derive from the COM component
// are not visible then we will give out the COM component's IProvideClassInfo.
if (pMT == NULL || pMT == g_pObjectClass)
{
SyncBlock* pSyncBlock = GetSyncBlock();
_ASSERTE(pSyncBlock);
RCWHolder pRCW(GetThread());
RCWPROTECT_BEGIN(pRCW, pSyncBlock);
bUseManagedIProvideClassInfo = !pRCW->SupportsIProvideClassInfo();
RCWPROTECT_END(pRCW);
}
// Object should always be visible if we return TRUE
_ASSERTE(!bUseManagedIProvideClassInfo || pMT != NULL);
return bUseManagedIProvideClassInfo;
}
// QI for well known interfaces from within the runtime direct fetch, instead of guid comparisons.
// The returned interface is AddRef'd.
IUnknown* SimpleComCallWrapper::QIStandardInterface(Enum_StdInterfaces index)
{
CONTRACT (IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
// assert for valid index
PRECONDITION(index < enum_LastStdVtable);
INSTANCE_CHECK;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
IUnknown* pIntf = NULL;
if (m_rgpVtable[index] != NULL)
{
pIntf = (IUnknown*)&m_rgpVtable[index];
}
else if (index == enum_IProvideClassInfo)
{
// If we either have a visible managed part to the class or if the base class
// does not implement IProvideClassInfo then use the one on the managed class.
if (ShouldUseManagedIProvideClassInfo())
{
// Set up the vtable pointer so that next time we don't have to determine
// that the IProvideClassInfo is provided by the managed class.
m_rgpVtable[enum_IProvideClassInfo] = g_rgStdVtables[enum_IProvideClassInfo];
// Return the interface pointer to the standard IProvideClassInfo interface.
pIntf = (IUnknown*)&m_rgpVtable[enum_IProvideClassInfo];
}
}
else if (index == enum_IErrorInfo)
{
if (SupportsExceptions(m_pMT))
{
// Initialize the exception info before we return the interface.
InitExceptionInfo();
pIntf = (IUnknown*)&m_rgpVtable[enum_IErrorInfo];
}
}
else if (index == enum_IDispatchEx)
{
if (SupportsIReflect(m_pMT))
{
// Initialize the DispatchExInfo before we return the interface.
InitDispatchExInfo();
pIntf = (IUnknown*)&m_rgpVtable[enum_IDispatchEx];
}
}
// If we found what we were looking for, then AddRef the wrapper.
// Note that we don't do SafeAddRef(pIntf) because it's overkill to
// go via IUnknown when we already have the wrapper in-hand.
if (pIntf)
{
if (index == enum_InnerUnknown)
this->AddRef();
else
this->AddRefWithAggregationCheck();
}
RETURN pIntf;
}
#include <optsmallperfcritical.h> // improves CCW QI perf by ~10%
#define IS_EQUAL_GUID(refguid,data1,data2,data3, data4,data5,data6,data7,data8,data9,data10,data11) \
((((DWORD*)&refguid)[0] == (data1)) && \
(((DWORD*)&refguid)[1] == (((data3)<<16)|(data2))) && \
(((DWORD*)&refguid)[2] == (((data7)<<24)|((data6)<<16)|((data5)<<8)|(data4))) && \
(((DWORD*)&refguid)[3] == (((data11)<<24)|((data10)<<16)|((data9)<<8)|(data8)))) \
#define IS_EQUAL_GUID_LOW_12_BYTES(refguid,data1,data2,data3, data4,data5,data6,data7,data8,data9,data10,data11) \
((((DWORD*)&refguid)[1] == (((data3)<<16)|(data2))) && \
(((DWORD*)&refguid)[2] == (((data7)<<24)|((data6)<<16)|((data5)<<8)|(data4))) && \
(((DWORD*)&refguid)[3] == (((data11)<<24)|((data10)<<16)|((data9)<<8)|(data8)))) \
#define HANDLE_IID_INLINE(itfEnum,data1,data2,data3, data4,data5,data6,data7,data8,data9,data10,data11) \
CASE_IID_INLINE(itfEnum,data1,data2,data3, data4,data5,data6,data7,data8,data9,data10,data11) \
{ \
RETURN QIStandardInterface(itfEnum); \
} \
break; \
#define CASE_IID_INLINE(itfEnum,data1,data2,data3, data4,data5,data6,data7,data8,data9,data10,data11) \
case data1: \
if (IS_EQUAL_GUID_LOW_12_BYTES(riid,data1,data2,data3, data4,data5,data6,data7,data8,data9,data10,data11)) \
#define IS_KNOWN_INTERFACE_CONTRACT(iid) \
CONTRACT(bool) \
{ \
MODE_ANY; \
NOTHROW; \
GC_NOTRIGGER; \
POSTCONDITION(RETVAL == !!IsEqualGUID(iid, riid)); \
} \
CONTRACT_END; \
inline bool IsIUnknown(REFIID riid)
{
IS_KNOWN_INTERFACE_CONTRACT(IID_IUnknown);
RETURN IS_EQUAL_GUID(riid, 0x00000000,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
}
inline bool IsIDispatch(REFIID riid)
{
IS_KNOWN_INTERFACE_CONTRACT(IID_IDispatch);
RETURN IS_EQUAL_GUID(riid, 0x00020400,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
}
inline bool IsGUID_NULL(REFIID riid)
{
IS_KNOWN_INTERFACE_CONTRACT(GUID_NULL);
RETURN IS_EQUAL_GUID(riid, 0x00000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00);
}
inline bool IsIErrorInfo(REFIID riid)
{
IS_KNOWN_INTERFACE_CONTRACT(IID_IErrorInfo);
RETURN IS_EQUAL_GUID(riid, 0x1CF2B120,0x547D,0x101B,0x8E,0x65,0x08,0x00,0x2B,0x2B,0xD1,0x19);
}
// QI for well known interfaces from within the runtime based on an IID.
IUnknown* SimpleComCallWrapper::QIStandardInterface(REFIID riid)
{
CONTRACT (IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INSTANCE_CHECK;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
// IID_IMarshal 00000003-0000-0000-C000-000000000046
// IID_IErrorInfo 1CF2B120-547D-101B-8E65-08002B2BD119
// IID_IDispatchEx A6EF9860-C720-11d0-9337-00A0C90DCAA9
// IID_IProvideClassInfo B196B283-BAB4-101A-B69C-00AA00341D07
// IID_IConnectionPointContainer B196B284-BAB4-101A-B69C-00AA00341D07
// IID_IObjectSafety CB5BDC81-93C1-11cf-8F20-00805F2CD064
// IID_ISupportErrorInfo DF0B3D60-548F-101B-8E65-08002B2BD119
// IID_IAgileObject 94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90
// Switch on the first DWORD since they're all (currently) unique.
switch (riid.Data1)
{
HANDLE_IID_INLINE(enum_IMarshal ,0x00000003,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
HANDLE_IID_INLINE(enum_IErrorInfo ,0x1CF2B120,0x547D,0x101B,0x8E,0x65,0x08,0x00,0x2B,0x2B,0xD1,0x19);
HANDLE_IID_INLINE(enum_IDispatchEx ,0xA6EF9860,0xC720,0x11d0,0x93,0x37,0x00,0xA0,0xC9,0x0D,0xCA,0xA9); // hit3, !=
HANDLE_IID_INLINE(enum_ISupportsErrorInfo ,0xDF0B3D60,0x548F,0x101B,0x8E,0x65,0x08,0x00,0x2B,0x2B,0xD1,0x19);
HANDLE_IID_INLINE(enum_IProvideClassInfo ,0xB196B283,0xBAB4,0x101A,0xB6,0x9C,0x00,0xAA,0x00,0x34,0x1D,0x07); // hit4, !=
HANDLE_IID_INLINE(enum_IConnectionPointContainer,0xB196B284,0xBAB4,0x101A,0xB6,0x9C,0x00,0xAA,0x00,0x34,0x1D,0x07); // b196b284 101abab4 aa009cb6 071d3400
CASE_IID_INLINE( enum_IAgileObject ,0x94ea2b94,0xe9cc,0x49e0,0xc0,0xff,0xee,0x64,0xca,0x8f,0x5b,0x90)
{
// Don't implement IAgileObject if we are aggregated, if the object explicitly implements IMarshal, or if its ICustomQI returns
// Failed or Handled for IID_IMarshal (compat).
if (!IsAggregated())
{
ComCallWrapperTemplate *pTemplate = GetComCallWrapperTemplate();
if (!pTemplate->ImplementsIMarshal())
{
if (!pTemplate->SupportsICustomQueryInterface() || !CustomQIRespondsToIMarshal())
{
RETURN QIStandardInterface(enum_IAgileObject);
}
}
}
}
break;
}
RETURN NULL;
}
#include <optdefault.h>
//--------------------------------------------------------------------------
// Init Outer unknown, cache a GIT cookie
//--------------------------------------------------------------------------
void SimpleComCallWrapper::InitOuter(IUnknown* pOuter)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pOuter, NULL_OK));
}
CONTRACTL_END;
if (pOuter != NULL)
m_pOuter = pOuter;
MarkAggregated();
}
//--------------------------------------------------------------------------
// Init Outer unknown, cache a GIT cookie
//--------------------------------------------------------------------------
void SimpleComCallWrapper::ResetOuter()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
m_pOuter = NULL;
if (IsAggregated())
UnMarkAggregated();
}
//--------------------------------------------------------------------------
// Get Outer Unknown on the correct thread
//--------------------------------------------------------------------------
IUnknown* SimpleComCallWrapper::GetOuter()
{
CONTRACT (IUnknown*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
RETURN m_pOuter;
}
BOOL SimpleComCallWrapper::FindConnectionPoint(REFIID riid, IConnectionPoint **ppCP)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(ppCP));
}
CONTRACTL_END;
// If the connection point list hasn't been set up yet, then set it up now.
if (!m_pCPList)
SetUpCPList();
// Search through the list for a connection point for the requested IID.
// Go to preemp mode early to prevent multiple GC mode switches.
GCX_PREEMP();
for (UINT i = 0; i < m_pCPList->Size(); i++)
{
ConnectionPoint *pCP = (*m_pCPList)[i];
if (pCP->GetIID() == riid)
{
// We found a connection point for the requested IID.
HRESULT hr = SafeQueryInterfacePreemp(pCP, IID_IConnectionPoint, (IUnknown**)ppCP);
_ASSERTE(hr == S_OK);
return TRUE;
}
}
return FALSE;
}
void SimpleComCallWrapper::EnumConnectionPoints(IEnumConnectionPoints **ppEnumCP)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(ppEnumCP));
}
CONTRACTL_END;
// If the connection point list hasn't been set up yet, then set it up now.
if (!m_pCPList)
SetUpCPList();
// Create a new connection point enum.
ComCallWrapper *pWrap = GetMainWrapper();
NewHolder<ConnectionPointEnum>pCPEnum = new ConnectionPointEnum(pWrap, m_pCPList);
// Retrieve the IEnumConnectionPoints interface. This cannot fail.
HRESULT hr = SafeQueryInterface((IUnknown*)pCPEnum, IID_IEnumConnectionPoints, (IUnknown**)ppEnumCP);
_ASSERTE(hr == S_OK);
pCPEnum.SuppressRelease();
}
//--------------------------------------------------------------------------
// COM called wrappers on COM+ objects
// Purpose: Expose COM+ objects as COM classic Interfaces
// Reqmts: Wrapper has to have the same layout as the COM2 interface
//
// The wrapper objects are aligned at 16 bytes, and the original this
// pointer is replicated every 16 bytes, so for any COM2 interface
// within the wrapper, the original 'this' can be obtained by masking
// low 4 bits of COM2 IP.
//
// 16 byte aligned COM2 Vtable
// +-----------+
// | Org. this |
// +-----------+ +-----+
// COM2 IP-->| VTable ptr|----------------------------->|slot1|
// +-----------+ +-----+ +-----+
// COM2 IP-->| VTable ptr|---------->|slot1| |slot2|
// +-----------+ +-----+ + +
// | VTable ptr| | ....| | ... |
// +-----------+ + + + +
// | Org. this | |slotN| |slotN|
// + + +-----+ +-----+
// | .... |
// + +
// | |
// +-----------+
//
//
// VTable and Stubs: can share stub code, we need to have different vtables
// for different interfaces, so the stub can jump to different
// marshalling code.
// Stubs : adjust this pointer and jump to the appropriate address,
// Marshalling params and results, based on the method signature the stub jumps to
// appropriate code to handle marshalling and unmarshalling.
//
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Check if the wrapper has been deactivated
//--------------------------------------------------------------------------
BOOL ComCallWrapper::IsHandleWeak()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SimpleComCallWrapper* simpleWrap = GetSimpleWrapper();
_ASSERTE(simpleWrap);
return simpleWrap->IsHandleWeak();
}
//--------------------------------------------------------------------------
// Mark the wrapper as holding a weak handle to the object
//--------------------------------------------------------------------------
void ComCallWrapper::MarkHandleWeak()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SimpleComCallWrapper* simpleWrap = GetSimpleWrapper();
_ASSERTE(simpleWrap);
simpleWrap->MarkHandleWeak();
}
//--------------------------------------------------------------------------
// Mark the wrapper as not having a weak handle
//--------------------------------------------------------------------------
void ComCallWrapper::ResetHandleStrength()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SimpleComCallWrapper* simpleWrap = GetSimpleWrapper();
_ASSERTE(simpleWrap);
simpleWrap->ResetHandleStrength();
}
//--------------------------------------------------------------------------
// Check if the wrapper was activated via COM
//--------------------------------------------------------------------------
BOOL ComCallWrapper::IsComActivated()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SimpleComCallWrapper* simpleWrap = GetSimpleWrapper();
_ASSERTE(simpleWrap);
return simpleWrap->IsComActivated();
}
//--------------------------------------------------------------------------
// Mark the wrapper as being created via COM activation
//--------------------------------------------------------------------------
VOID ComCallWrapper::MarkComActivated()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
SimpleComCallWrapper* simpleWrap = GetSimpleWrapper();
_ASSERTE(simpleWrap);
simpleWrap->MarkComActivated();
}
//--------------------------------------------------------------------------
// void ComCallWrapper::InitializeOuter(IUnknown* pOuter)
// init outer unknown, aggregation support
//--------------------------------------------------------------------------
void ComCallWrapper::InitializeOuter(IUnknown* pOuter)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pOuter, NULL_OK));
}
CONTRACTL_END;
GetSimpleWrapper()->InitOuter(pOuter);
}
//--------------------------------------------------------------------------
// BOOL ComCallWrapper::IsAggregated()
// check if the wrapper is aggregated
//--------------------------------------------------------------------------
BOOL ComCallWrapper::IsAggregated()
{
WRAPPER_NO_CONTRACT;
return GetSimpleWrapper()->IsAggregated();
}
//--------------------------------------------------------------------------
// BOOL ComCallWrapper::IsExtendsCOMObject(()
// check if the wrapper is to a managed object that extends a com object
//--------------------------------------------------------------------------
BOOL ComCallWrapper::IsExtendsCOMObject()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
return GetSimpleWrapper()->IsExtendsCOMObject();
}
//--------------------------------------------------------------------------
// HRESULT ComCallWrapper::GetInnerUnknown(void** ppv)
// aggregation support, get inner unknown
//--------------------------------------------------------------------------
HRESULT ComCallWrapper::GetInnerUnknown(void **ppv)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(ppv));
PRECONDITION(GetSimpleWrapper()->GetOuter() != NULL);
}
CONTRACTL_END;
return GetSimpleWrapper()->GetInnerUnknown(ppv);
}
//--------------------------------------------------------------------------
// Get Outer Unknown on the correct thread
//--------------------------------------------------------------------------
IUnknown* ComCallWrapper::GetOuter()
{
CONTRACT (IUnknown*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
RETURN GetSimpleWrapper()->GetOuter();
}
//--------------------------------------------------------------------------
// SyncBlock* ComCallWrapper::GetSyncBlock()
//--------------------------------------------------------------------------
SyncBlock* ComCallWrapper::GetSyncBlock()
{
CONTRACT (SyncBlock*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
RETURN GetSimpleWrapper()->GetSyncBlock();
}
//--------------------------------------------------------------------------
//ComCallWrapper* ComCallWrapper::CopyFromTemplate(ComCallWrapperTemplate* pTemplate,
// OBJECTREF* pRef)
// create a wrapper and initialize it from the template
//--------------------------------------------------------------------------
ComCallWrapper* ComCallWrapper::CopyFromTemplate(ComCallWrapperTemplate* pTemplate,
ComCallWrapperCache *pWrapperCache,
OBJECTHANDLE oh)
{
CONTRACT (ComCallWrapper*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(pTemplate));
PRECONDITION(CheckPointer(pWrapperCache));
PRECONDITION(oh != NULL);
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
// num interfaces on the object
size_t numInterfaces = pTemplate->GetNumInterfaces();
// we have a template, create a wrapper and initialize from the template
// alloc wrapper, aligned to cache line
NewCCWHolder pStartWrapper(pWrapperCache);
pStartWrapper = (ComCallWrapper*)pWrapperCache->GetCacheLineAllocator()->
#ifdef HOST_64BIT
GetCacheLine64();
_ASSERTE(sizeof(ComCallWrapper) <= 64);
#else
GetCacheLine32();
_ASSERTE(sizeof(ComCallWrapper) <= 32);
#endif
if (pStartWrapper == NULL)
COMPlusThrowOM();
LOG((LF_INTEROP, LL_INFO100, "ComCallWrapper::CopyFromTemplate on Object %8.8x, Wrapper %8.8x\n", oh, static_cast<ComCallWrapperPtr>(pStartWrapper)));
// addref commgr
pWrapperCache->AddRef();
// store the object handle
pStartWrapper->m_ppThis = oh;
// The first slot will hold the Basic interface.
// The second slot will hold the IClassX interface which will be generated on the fly.
unsigned blockIndex = 0;
if (pTemplate->RepresentsVariantInterface())
{
// interface CCW doesn't need the basic ComMT, it will fall back to its class CCW
// for anything but the one variant interface it represents
pStartWrapper->m_rgpIPtr[blockIndex++] = NULL;
}
else
{
pStartWrapper->m_rgpIPtr[blockIndex++] = (SLOT *)(pTemplate->GetBasicComMT() + 1);
}
pStartWrapper->m_rgpIPtr[blockIndex++] = NULL;
ComCallWrapper* pWrapper = pStartWrapper;
for (unsigned i =0; i< numInterfaces; i++)
{
if (blockIndex >= NumVtablePtrs)
{
// alloc wrapper, aligned 32 bytes
ComCallWrapper* pNewWrapper = (ComCallWrapper*)pWrapperCache->GetCacheLineAllocator()->
#ifdef HOST_64BIT
GetCacheLine64();
_ASSERTE(sizeof(ComCallWrapper) <= 64);
#else
GetCacheLine32();
_ASSERTE(sizeof(ComCallWrapper) <= 32);
#endif
_ASSERTE(0 == (((DWORD_PTR)pNewWrapper) & ~enum_ThisMask));
// Link the wrapper
SetNext(pWrapper, pNewWrapper);
blockIndex = 0; // reset block index
if (pNewWrapper == NULL)
{
RETURN NULL;
}
pWrapper = pNewWrapper;
// initialize the object reference
pWrapper->m_ppThis = oh;
}
pWrapper->m_rgpIPtr[blockIndex++] = pTemplate->GetVTableSlot(i);
}
// If the wrapper is part of a chain, then set the terminator.
if (pWrapper != pStartWrapper)
SetNext(pWrapper, LinkedWrapperTerminator);
pStartWrapper.SuppressRelease();
RETURN pStartWrapper;
}
//--------------------------------------------------------------------------
// identify the location within the wrapper where the vtable for this index will
// be stored
//--------------------------------------------------------------------------
SLOT** ComCallWrapper::GetComIPLocInWrapper(ComCallWrapper* pWrap, unsigned int iIndex)
{
CONTRACT (SLOT**)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pWrap));
PRECONDITION(iIndex > 1); // We should never attempt to get the basic or IClassX interface here.
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
SLOT** pTearOff = NULL;
while (iIndex >= NumVtablePtrs)
{
//@todo delayed creation support
_ASSERTE(pWrap->IsLinked() != 0);
pWrap = GetNext(pWrap);
iIndex-= NumVtablePtrs;
}
_ASSERTE(pWrap != NULL);
pTearOff = (SLOT **)&pWrap->m_rgpIPtr[iIndex];
RETURN pTearOff;
}
//--------------------------------------------------------------------------
// void ComCallWrapper::Cleanup(ComCallWrapper* pWrap)
// clean up , release gc registered reference and free wrapper
//--------------------------------------------------------------------------
void ComCallWrapper::Cleanup()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
INSTANCE_CHECK;
}
CONTRACTL_END;
_ASSERTE(m_pSimpleWrapper);
// Save it into a variable to observe a consistent state
ULONG refCount = m_pSimpleWrapper->GetRefCount();
LOG((LF_INTEROP, LL_INFO100,
"Calling ComCallWrapper::Cleanup on CCW 0x%p. cbRef = 0x%x\n",
this, refCount));
if (refCount != 0)
{
// _ASSERTE(g_fEEShutDown == TRUE);
// could be either in shutdown or forced GC in appdomain unload
// there are external COM references to this wrapper
// so let us just forget about cleaning now
// when the ref-count reaches 0, we will
// do the cleanup anyway
return;
}
STRESS_LOG1 (LF_INTEROP, LL_INFO100, "Cleaning up CCW 0x%p\n", this);
// Retrieve the COM call wrapper cache before we clear anything
ComCallWrapperCache *pWrapperCache = m_pSimpleWrapper->GetWrapperCache();
BOOL fOwnsHandle = FALSE;
SyncBlock* pSyncBlock = m_pSimpleWrapper->GetSyncBlock();
// only the "root" CCW owns the handle
// Even though we don't use this for native deriving from managed scenarios,
// we still use multiple CCWs from variance
fOwnsHandle = !(GetComCallWrapperTemplate()->RepresentsVariantInterface());
// This CCW may have belonged to an object that was killed when its AD was unloaded, but the CCW has a positive RefCount.
// In this case, the SyncBlock and/or InteropSyncBlockInfo will be null.
if (pSyncBlock)
{
InteropSyncBlockInfo* pInteropInfo = pSyncBlock->GetInteropInfoNoCreate();
if (pInteropInfo)
{
// Disconnect the object from the CCW
// Starting now, if this object gets passed out
// to unmanaged code, it will create a new CCW tied
// to the domain it was passed out from.
pInteropInfo->SetCCW(NULL);
// NULL the syncblock entry - we can't hang onto this anymore as the syncblock will be killed asynchronously to us.
m_pSimpleWrapper->ResetSyncBlock();
// Check for an associated RCW
RCWHolder pRCW(GetThread());
pRCW.InitNoCheck(pSyncBlock);
NewRCWHolder pNewRCW = pRCW.GetRawRCWUnsafe();
if (!pRCW.IsNull())
{
// Remove the RCW from the cache
RCWCache* pCache = RCWCache::GetRCWCacheNoCreate();
_ASSERTE(pCache);
{
// Switch to cooperative mode for RCWCache::LockHolder::LockHolder (COOPERATIVE)
GCX_COOP();
RCWCache::LockHolder lh(pCache);
pCache->RemoveWrapper(&pRCW);
}
}
}
}
if (m_pSimpleWrapper)
{
m_pSimpleWrapper->Cleanup();
}
if (g_fEEStarted || m_pSimpleWrapper->GetOuter() == NULL)
{
delete m_pSimpleWrapper;
ClearSimpleWrapper(this);
}
if (fOwnsHandle && m_ppThis)
{
LOG((LF_INTEROP, LL_INFO100, "ComCallWrapper::Cleanup on Object %8.8x\n", m_ppThis));
ClearHandle();
}
m_ppThis = NULL;
FreeWrapper(pWrapperCache);
}
//--------------------------------------------------------------------------
// void ComCallWrapper::Neuter()
// walk the CCW list and clear all handles to the object
//--------------------------------------------------------------------------
void ComCallWrapper::Neuter()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
ClearHandle();
ComCallWrapper* pWrap = this;
while (pWrap != NULL)
{
ComCallWrapper* pTempWrap = ComCallWrapper::GetNext(pWrap);
pWrap->m_ppThis = NULL;
pWrap = pTempWrap;
}
}
//--------------------------------------------------------------------------
// void ComCallWrapper::ClearHandle()
// clear the ref-counted handle
//--------------------------------------------------------------------------
void ComCallWrapper::ClearHandle()
{
WRAPPER_NO_CONTRACT;
OBJECTHANDLE pThis = m_ppThis;
if (InterlockedCompareExchangeT(&m_ppThis, NULL, pThis) == pThis)
{
DestroyRefcountedHandle(pThis);
}
}
SLOT** ComCallWrapper::GetFirstInterfaceSlot()
{
CONTRACT(SLOT**)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
SLOT** firstInterface = GetComIPLocInWrapper(this, Slot_FirstInterface);
RETURN firstInterface;
}
//--------------------------------------------------------------------------
// void ComCallWrapper::FreeWrapper(ComCallWrapper* pWrap)
// walk the list and free all wrappers
//--------------------------------------------------------------------------
void ComCallWrapper::FreeWrapper(ComCallWrapperCache *pWrapperCache)
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
INSTANCE_CHECK;
PRECONDITION(CheckPointer(pWrapperCache));
}
CONTRACTL_END;
{
ComCallWrapperCache::LockHolder lh(pWrapperCache);
ComCallWrapper* pWrap2 = IsLinked() ? GetNext(this) : NULL;
while (pWrap2 != NULL)
{
ComCallWrapper* pTempWrap = GetNext(pWrap2);
#ifdef HOST_64BIT
pWrapperCache->GetCacheLineAllocator()->FreeCacheLine64(pWrap2);
#else //HOST_64BIT
pWrapperCache->GetCacheLineAllocator()->FreeCacheLine32(pWrap2);
#endif //HOST_64BIT
pWrap2 = pTempWrap;
}
#ifdef HOST_64BIT
pWrapperCache->GetCacheLineAllocator()->FreeCacheLine64(this);
#else //HOST_64BIT
pWrapperCache->GetCacheLineAllocator()->FreeCacheLine32(this);
#endif //HOST_64BIT
}
// release ccw mgr
pWrapperCache->Release();
}
//--------------------------------------------------------------------------
//ComCallWrapper* ComCallWrapper::CreateWrapper(OBJECTREF* ppObj)
// this function should be called only with pre-emptive GC disabled
// GCProtect the object ref being passed in, as this code could enable gc
//--------------------------------------------------------------------------
ComCallWrapper* ComCallWrapper::CreateWrapper(OBJECTREF* ppObj)
{
CONTRACT(ComCallWrapper *)
{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
PRECONDITION(ppObj != NULL);
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
ComCallWrapper* pStartWrapper = NULL;
OBJECTREF pServer = NULL;
GCPROTECT_BEGIN(pServer);
pServer = *ppObj;
// grab the sync block from the server
SyncBlock* pSyncBlock = pServer->GetSyncBlock();
pSyncBlock->SetPrecious();
// if the object belongs to a domain neutral class, need to allocate the wrapper in the default domain.
// The object is potentially agile so if allocate out of the current domain and then hand out to
// multiple domains we might never release the wrapper for that object and hence never unload the CCWC.
ComCallWrapperCache *pWrapperCache = NULL;
TypeHandle thClass = pServer->GetTypeHandle();
pWrapperCache = thClass.GetMethodTable()->GetLoaderAllocator()->GetComCallWrapperCache();
{
// check if somebody beat us to it
pStartWrapper = GetWrapperForObject(pServer);
if (pStartWrapper == NULL)
{
// get the wrapper template from object's type
ComCallWrapperTemplate* pTemplate = ComCallWrapperTemplate::GetTemplate(thClass);
// Make sure the CCW will be destroyed when exception happens
// Also keep pWrapperCache alive within this scope
// It needs to be destroyed after ComCallWrapperCache::LockHolder otherwise there would be a lock violation
NewCCWHolder pNewCCW(pWrapperCache);
// Now we'll take the lock in a place where we won't be calling managed code and check again.
{
ComCallWrapperCache::LockHolder lh(pWrapperCache);
pStartWrapper = GetWrapperForObject(pServer, pTemplate);
if (pStartWrapper == NULL)
{
Wrapper<OBJECTHANDLE, DoNothing, DestroyRefcountedHandle> oh;
ComCallWrapper *pRootWrapper = GetWrapperForObject(pServer, NULL);
if (pRootWrapper == NULL)
{
// create handle for the object. This creates a handle in the current domain. We can't tell
// if the object is agile in non-checked, so we trust that our checking works and when we
// attempt to hand this out to another domain then we will assume that the object is truly
// agile and will convert the handle to a global handle.
oh = GetAppDomain()->CreateRefcountedHandle(NULL);
_ASSERTE(oh);
}
else
{
// if the object already has a CCW, we reuse the handle
oh = pRootWrapper->GetObjectHandle();
oh.SuppressRelease();
}
// copy from template
pNewCCW = CopyFromTemplate(pTemplate, pWrapperCache, oh);
NewHolder<SimpleComCallWrapper> pSimpleWrap = new SimpleComCallWrapper();
pSimpleWrap->InitNew(pServer, pWrapperCache, pNewCCW, pSyncBlock, pTemplate);
InitSimpleWrapper(pNewCCW, pSimpleWrap);
if (pRootWrapper == NULL)
{
// store the object in the handle - this must happen before we publish the CCW
// in the sync block, so that other threads don't see a CCW pointing to nothing
StoreObjectInHandle( oh, pServer );
// finally, store the wrapper for the object in the sync block
pSyncBlock->GetInteropInfo()->SetCCW(pNewCCW);
}
else
{
// link the wrapper to the existing chain of CCWs
while (ComCallWrapper::GetNext(pRootWrapper) != NULL)
{
pRootWrapper = ComCallWrapper::GetNext(pRootWrapper);
}
ComCallWrapper::SetNext(pRootWrapper, pNewCCW);
}
oh.SuppressRelease();
pNewCCW.SuppressRelease();
pSimpleWrap.SuppressRelease();
pStartWrapper = pNewCCW;
}
}
}
}
GCPROTECT_END();
RETURN pStartWrapper;
}
//--------------------------------------------------------------------------
// Get IClassX interface pointer from the wrapper. This method will also
// lay out the IClassX COM method table if it has not yet been laid out.
// The returned interface is AddRef'd.
//--------------------------------------------------------------------------
IUnknown* ComCallWrapper::GetIClassXIP(bool inspectionOnly)
{
CONTRACT (IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
ComCallWrapper *pWrap = this;
IUnknown *pIntf = NULL;
ComMethodTable* pIClassXComMT = NULL;
// The IClassX VTable pointer is in the start wrapper.
if (pWrap->IsLinked())
pWrap = ComCallWrapper::GetStartWrapper(pWrap);
SLOT* slot = pWrap->m_rgpIPtr[Slot_IClassX];
if (NULL == slot)
{
if (inspectionOnly)
RETURN NULL;
// Get the IClassX ComMethodTable (create if it doesn't exist),
// and set it into the vtable map.
pIClassXComMT = m_pSimpleWrapper->m_pTemplate->GetClassComMT();
pWrap->m_rgpIPtr[Slot_IClassX] = (SLOT *)(pIClassXComMT + 1);
}
else
{
pIClassXComMT = (ComMethodTable*)slot - 1;
}
// Lay out of the IClassX COM method table if it has not yet been laid out.
if (!pIClassXComMT->IsLayoutComplete())
{
// We won't attempt to lay out the class if we are only trying to
// passively inspect the interface.
if (inspectionOnly)
RETURN NULL;
else
pIClassXComMT->LayOutClassMethodTable();
}
// Return the IClassX vtable pointer.
pIntf = (IUnknown*)&pWrap->m_rgpIPtr[Slot_IClassX];
// If we are only inspecting, don't addref.
if (inspectionOnly)
RETURN pIntf;
// AddRef the wrapper.
// Note that we don't do SafeAddRef(pIntf) because it's overkill to
// go via IUnknown when we already have the wrapper in-hand.
ULONG cbRef = pWrap->AddRefWithAggregationCheck();
// 0xbadF00d implies the AddRef didn't go through
RETURN ((cbRef != 0xbadf00d) ? pIntf : NULL);
}
IUnknown* ComCallWrapper::GetBasicIP(bool inspectionOnly)
{
CONTRACT (IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
// If the legacy switch is set, we'll always return the IClassX IP
// when QIing for IUnknown or IDispatch.
// Whidbey Tactics has decided to make this opt-in rather than
// opt-out for now. Remove the check for the legacy switch.
if (GetComCallWrapperTemplate()->SupportsIClassX())
RETURN GetIClassXIP(inspectionOnly);
ComCallWrapper *pWrap = this;
IUnknown *pIntf = NULL;
// The IClassX VTable pointer is in the start wrapper.
if (pWrap->IsLinked())
pWrap = ComCallWrapper::GetStartWrapper(pWrap);
ComMethodTable* pIBasicComMT = (ComMethodTable*)pWrap->m_rgpIPtr[Slot_Basic] - 1;
_ASSERTE(pIBasicComMT);
// Lay out the basic COM method table if it has not yet been laid out.
if (!pIBasicComMT->IsLayoutComplete())
{
if (inspectionOnly)
RETURN NULL;
else
pIBasicComMT->LayOutBasicMethodTable();
}
// Return the basic vtable pointer.
pIntf = (IUnknown*)&pWrap->m_rgpIPtr[Slot_Basic];
// If we are not addref'ing the IUnknown (for passive inspection like ETW), return it now.
if (inspectionOnly)
RETURN pIntf;
// AddRef the wrapper.
// Note that we don't do SafeAddRef(pIntf) because it's overkill to
// go via IUnknown when we already have the wrapper in-hand.
ULONG cbRef = pWrap->AddRefWithAggregationCheck();
// 0xbadF00d implies the AddRef didn't go through
RETURN ((cbRef != 0xbadf00d) ? pIntf : NULL);
}
struct InvokeICustomQueryInterfaceGetInterfaceArgs
{
ComCallWrapper *pWrap;
GUID *pGuid;
IUnknown **ppUnk;
CustomQueryInterfaceResult *pRetVal;
};
VOID __stdcall InvokeICustomQueryInterfaceGetInterface_CallBack(LPVOID ptr)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(ptr));
}
CONTRACTL_END;
InvokeICustomQueryInterfaceGetInterfaceArgs *pArgs = (InvokeICustomQueryInterfaceGetInterfaceArgs*)ptr;
{
GCX_COOP();
OBJECTREF pObj = pArgs->pWrap->GetObjectRef();
GCPROTECT_BEGIN(pObj);
// 1. Get MD
MethodDesc *pMD = pArgs->pWrap->GetSimpleWrapper()->GetComCallWrapperTemplate()->GetICustomQueryInterfaceGetInterfaceMD();
// 2. Get Object Handle
OBJECTHANDLE hndCustomQueryInterface = pArgs->pWrap->GetObjectHandle();
// 3 construct the MethodDescCallSite
MethodDescCallSite GetInterface(pMD, hndCustomQueryInterface);
ARG_SLOT Args[] = {
ObjToArgSlot(pObj),
PtrToArgSlot(pArgs->pGuid),
PtrToArgSlot(pArgs->ppUnk),
};
*(pArgs->pRetVal) = (CustomQueryInterfaceResult)GetInterface.Call_RetArgSlot(Args);
GCPROTECT_END();
}
}
//--------------------------------------------------------------------------
// check if the interface is supported, return a index into the IMap
// returns -1, if pIntfMT is not supported
//--------------------------------------------------------------------------
static int GetIndexForIntfMT(ComCallWrapperTemplate *pTemplate, MethodTable *pIntfMT)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pTemplate));
PRECONDITION(CheckPointer(pIntfMT));
}
CONTRACTL_END;
for (ULONG j = 0; j < pTemplate->GetNumInterfaces(); j++)
{
ComMethodTable *pItfComMT = (ComMethodTable *)pTemplate->GetVTableSlot(j) - 1;
if (pItfComMT->GetMethodTable()->IsEquivalentTo(pIntfMT))
return j;
}
return -1;
}
static IUnknown *GetComIPFromCCW_VisibilityCheck(
IUnknown *pIntf,
MethodTable *pIntfMT,
ComMethodTable *pIntfComMT,
GetComIPFromCCW::flags flags)
{
CONTRACT(IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pIntf));
PRECONDITION(CheckPointer(pIntfComMT));
}
CONTRACT_END;
if (// Do a visibility check if needed.
((flags & GetComIPFromCCW::CheckVisibility) && (!pIntfComMT->IsComVisible())))
{
// If not, fail to return the interface.
SafeRelease(pIntf);
RETURN NULL;
}
RETURN pIntf;
}
static IUnknown * GetComIPFromCCW_HandleExtendsCOMObject(
ComCallWrapper * pWrap,
REFIID riid,
MethodTable * pIntfMT,
ComCallWrapperTemplate * pTemplate,
int imapIndex,
unsigned int intfIndex)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
// If we don't implement the interface, we delegate to base
BOOL bDelegateToBase = TRUE;
if (imapIndex != -1)
{
MethodTable * pMT = pWrap->GetMethodTableOfObjectRef();
MethodTable::InterfaceMapIterator intIt = pMT->IterateInterfaceMapFrom(intfIndex);
// If the number of slots is 0, then no need to proceed
MethodTable* pItf = intIt.GetInterfaceApprox();
if (pItf->GetNumVirtuals() != 0)
{
MethodDesc *pClsMD = NULL;
_ASSERTE(!pItf->HasInstantiation());
// Find the implementation for the first slot of the interface
DispatchSlot impl(pMT->FindDispatchSlot(pItf->GetTypeID(), 0, FALSE /* throwOnConflict */));
CONSISTENCY_CHECK(!impl.IsNull());
// Get the MethodDesc for this slot in the class
pClsMD = impl.GetMethodDesc();
MethodTable * pClsMT = pClsMD->GetMethodTable();
bDelegateToBase = (pClsMT->IsInterface() || pClsMT->IsComImport()) ? TRUE : FALSE;
}
else
{
// The interface has no methods so we cannot override it. Because of this
// it makes sense to delegate to the base COM component.
bDelegateToBase = TRUE;
}
}
if (bDelegateToBase)
{
// This is an interface of the base COM object so delegate the call to it
SyncBlock* pBlock = pWrap->GetSyncBlock();
_ASSERTE(pBlock);
SafeComHolder<IUnknown> pUnk;
RCWHolder pRCW(GetThread());
RCWPROTECT_BEGIN(pRCW, pBlock);
pUnk = (pIntfMT != NULL) ? pRCW->GetComIPFromRCW(pIntfMT)
: pRCW->GetComIPFromRCW(riid);
RCWPROTECT_END(pRCW);
return pUnk.Extract();
}
return NULL;
}
static IUnknown * GetComIPFromCCW_ForIID_Worker(
ComCallWrapper *pWrap,
REFIID riid,
MethodTable *pIntfMT,
GetComIPFromCCW::flags flags,
ComCallWrapperTemplate * pTemplate)
{
CONTRACT(IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pWrap));
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
ComMethodTable * pIntfComMT = NULL;
MethodTable * pMT = pWrap->GetMethodTableOfObjectRef();
// At this point, it must be that the IID is one of IClassX IIDs or
// it isn't implemented on this class. We'll have to search through and set
// up the entire hierarchy to determine which it is.
if (IsIClassX(pMT, riid, &pIntfComMT))
{
// If the class that this IClassX's was generated for is marked
// as ClassInterfaceType.AutoDual or AutoDisp,
// then give out the IClassX IP.
if (pIntfComMT->GetClassInterfaceType() == clsIfAutoDual || pIntfComMT->GetClassInterfaceType() == clsIfAutoDisp)
{
// Make sure the all the base classes of the class this IClassX corresponds to
// are visible to COM.
pIntfComMT->CheckParentComVisibility(FALSE);
// Giveout IClassX of this class because the IID matches one of the IClassX in the hierarchy
// This assumes any IClassX implementation must be derived from base class IClassX's implementation
IUnknown * pIntf = pWrap->GetIClassXIP();
RETURN GetComIPFromCCW_VisibilityCheck(pIntf, pIntfMT, pIntfComMT, flags);
}
}
RETURN NULL;
}
static IUnknown *GetComIPFromCCW_ForIntfMT_Worker(ComCallWrapper *pWrap, MethodTable *pIntfMT, GetComIPFromCCW::flags flags)
{
CONTRACT(IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pWrap));
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
MethodTable * pMT = pWrap->GetMethodTableOfObjectRef();
// class method table
if (pMT->CanCastToClass(pIntfMT))
{
// Make sure we're not trying to pass out a generic-based class interface
if (pMT->HasInstantiation())
{
COMPlusThrow(kInvalidOperationException, IDS_EE_ATTEMPT_TO_CREATE_GENERIC_CCW);
}
// Retrieve the COM method table for the requested interface.
ComCallWrapperTemplate *pIntfCCWTemplate = ComCallWrapperTemplate::GetTemplate(TypeHandle(pIntfMT));
if (pIntfCCWTemplate->SupportsIClassX())
{
ComMethodTable * pIntfComMT = pIntfCCWTemplate->GetClassComMT();
// If the class that this IClassX's was generated for is marked
// as ClassInterfaceType.AutoDual or AutoDisp,
// then give out the IClassX IP.
if (pIntfComMT->GetClassInterfaceType() == clsIfAutoDual || pIntfComMT->GetClassInterfaceType() == clsIfAutoDisp)
{
// Make sure the all the base classes of the class this IClassX corresponds to
// are visible to COM.
pIntfComMT->CheckParentComVisibility(FALSE);
// Giveout IClassX
IUnknown * pIntf = pWrap->GetIClassXIP();
RETURN GetComIPFromCCW_VisibilityCheck(pIntf, pIntfMT, pIntfComMT, flags);
}
}
}
RETURN NULL;
}
static bool GetComIPFromCCW_HandleCustomQI(
ComCallWrapper * pWrap, REFIID riid, MethodTable * pIntfMT, IUnknown ** ppUnkOut)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pWrap));
}
CONTRACTL_END;
// Customize QI: We call the method System.Runtime.InteropServices.ICustomQueryInterface
// GetInterface implemented by user to do the customized QI work.
CustomQueryInterfaceResult retVal = Handled;
// prepare the GUID
GUID guid;
if (IsEqualGUID(riid, GUID_NULL) && pIntfMT != NULL)
{
// riid is null, we retrieve the guid from the methodtable
pIntfMT->GetGuid(&guid, true);
}
else
{
// copy riid to avoid user modify it
guid = riid;
}
InvokeICustomQueryInterfaceGetInterfaceArgs args = {pWrap, &guid, ppUnkOut, &retVal};
InvokeICustomQueryInterfaceGetInterface_CallBack(&args);
// return if user already handle the QI
if (retVal == Handled)
return true;
// return NULL if user wants to fail the QI
if (retVal == Failed)
{
*ppUnkOut = NULL;
return true;
}
// assure that user returns the known return value
_ASSERTE(retVal == NotHandled);
return false;
}
// A MODE_ANY helper to get the MethodTable of the 'this' object. This helper keeps
// the GCX_COOP transition out of the caller (it implies a holder which implies an
// FS:0 handler on x86).
MethodTable * ComCallWrapper::GetMethodTableOfObjectRef()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
GCX_COOP();
return GetObjectRef()->GetMethodTable();
}
//--------------------------------------------------------------------------
// IUnknown* ComCallWrapper::GetComIPfromCCW(ComCallWrapper *pWrap, REFIID riid, MethodTable* pIntfMT, BOOL bCheckVisibility)
// Get an interface from wrapper, based on riid or pIntfMT. The returned interface is AddRef'd.
//--------------------------------------------------------------------------
// static
IUnknown* ComCallWrapper::GetComIPFromCCW(ComCallWrapper *pWrap, REFIID riid, MethodTable* pIntfMT,
GetComIPFromCCW::flags flags)
{
CONTRACT(IUnknown*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pWrap));
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
// scan the wrapper
if (pWrap->IsLinked())
pWrap = ComCallWrapper::GetStartWrapper(pWrap);
ComCallWrapperTemplate *pTemplate = pWrap->GetSimpleWrapper()->GetComCallWrapperTemplate();
// We should not be getting a CCW that represents a CCW interface as GetWrapperFromIP will always
// convert a IP to main CCW
_ASSERTE(!pTemplate->RepresentsVariantInterface());
if (IsIUnknown(riid))
{
// We don't do visibility checks on IUnknown.
RETURN pWrap->GetBasicIP();
}
if (!(flags & GetComIPFromCCW::SuppressCustomizedQueryInterface)
&& pTemplate->SupportsICustomQueryInterface())
{
// Customize QI: We call the method System.Runtime.InteropServices.ICustomQueryInterface
// GetInterface implemented by user to do the customized QI work.
IUnknown * pUnkCustomQIResult = NULL;
if (GetComIPFromCCW_HandleCustomQI(pWrap, riid, pIntfMT, &pUnkCustomQIResult))
RETURN pUnkCustomQIResult;
}
if (IsIDispatch(riid))
{
// We don't do visibility checks on IUnknown.
RETURN pWrap->GetIDispatchIP();
}
signed imapIndex = -1;
if (pIntfMT == NULL)
{
if (IsGUID_NULL(riid)) // there's no interface with GUID_NULL IID so we can bail out right away
RETURN NULL;
// Go through all the implemented methods except the COM imported class interfaces
// and compare the IID's to find the requested one.
for (unsigned j = 0; j < pTemplate->GetNumInterfaces(); j++)
{
ComMethodTable *pItfComMT = (ComMethodTable *)pTemplate->GetVTableSlot(j) - 1;
if (pItfComMT && !pItfComMT->IsComClassItf())
{
if (InlineIsEqualGUID(pItfComMT->GetIID(), riid))
{
pIntfMT = pItfComMT->GetMethodTable();
imapIndex = j;
break;
}
}
}
if (imapIndex == -1)
{
// Check for the standard interfaces.
SimpleComCallWrapper* pSimpleWrap = pWrap->GetSimpleWrapper();
IUnknown * pIntf = pSimpleWrap->QIStandardInterface(riid);
if (pIntf)
RETURN pIntf;
pIntf = GetComIPFromCCW_ForIID_Worker(pWrap, riid, pIntfMT, flags, pTemplate);
if (pIntf)
RETURN pIntf;
}
}
else
{
imapIndex = GetIndexForIntfMT(pTemplate, pIntfMT);
if (!pIntfMT->IsInterface())
{
IUnknown * pIntf = GetComIPFromCCW_ForIntfMT_Worker(pWrap, pIntfMT, flags);
if (pIntf)
RETURN pIntf;
}
}
// At this point, all of the 'fast' special cases have already returned and we're
// left with either no interface found (imapIndex == -1) or a user-code-implemented
// interface was found ((imapIndex != -1) && (pIntfMT != NULL)).
unsigned intfIndex = imapIndex;
if (imapIndex != -1)
{
// We don't support QI calls for interfaces that have generic arguments.
_ASSERTE(pIntfMT != NULL);
if (pIntfMT->HasInstantiation())
{
COMPlusThrow(kInvalidOperationException, IDS_EE_ATTEMPT_TO_CREATE_GENERIC_CCW);
}
if (pIntfMT->IsInterface() && !pIntfMT->HasOnlyAbstractMethods())
{
COMPlusThrow(kInvalidOperationException, IDS_EE_ATTEMPT_TO_CREATE_NON_ABSTRACT_CCW);
}
// The first block has one slot for the IClassX vtable pointer
// and one slot for the basic vtable pointer.
imapIndex += Slot_FirstInterface;
}
// COM plus objects that extend from COM objects are special
if (pWrap->IsExtendsCOMObject())
{
IUnknown * pIntf = GetComIPFromCCW_HandleExtendsCOMObject(pWrap, riid, pIntfMT,
pTemplate, imapIndex, intfIndex);
if (pIntf)
RETURN pIntf;
}
// check if interface is supported
if (imapIndex == -1)
RETURN NULL;
// interface method table != NULL
_ASSERTE(pIntfMT != NULL);
// IUnknown* loc within the wrapper
SLOT** ppVtable = GetComIPLocInWrapper(pWrap, imapIndex);
_ASSERTE(*ppVtable != NULL); // this should point to COM Vtable or interface vtable
// Finish laying out the interface COM method table if it has not been done yet.
ComMethodTable *pItfComMT = ComMethodTable::ComMethodTableFromIP((IUnknown*)ppVtable);
if (!pItfComMT->IsLayoutComplete())
{
MethodTable *pClassMT = pTemplate->GetClassType().GetMethodTable();
if (!pItfComMT->LayOutInterfaceMethodTable(pClassMT))
RETURN NULL;
}
// AddRef the wrapper.
// Note that we don't do SafeAddRef(pIntf) because it's overkill to
// go via IUnknown when we already have the wrapper in-hand.
ULONG cbRef = pWrap->AddRefWithAggregationCheck();
// 0xbadF00d implies the AddRef didn't go through
if (cbRef == 0xbadf00d)
RETURN NULL;
// The interface pointer is the pointer to the vtable.
IUnknown * pIntf = (IUnknown*)ppVtable;
// Retrieve the COM method table from the interface.
ComMethodTable * pIntfComMT = ComMethodTable::ComMethodTableFromIP(pIntf);
// Manual inlining of GetComIPFromCCW_VisibilityCheck() for common case.
if (// Do a visibility check if needed.
((flags & GetComIPFromCCW::CheckVisibility) && (!pIntfComMT->IsComVisible())))
{
// If not, fail to return the interface.
SafeRelease(pIntf);
pIntf = NULL;
}
RETURN pIntf;
}
//--------------------------------------------------------------------------
// Get the IDispatch interface pointer for the wrapper.
// The returned interface is AddRef'd.
//--------------------------------------------------------------------------
IDispatch* ComCallWrapper::GetIDispatchIP()
{
CONTRACT (IDispatch*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
SimpleComCallWrapper* pSimpleWrap = GetSimpleWrapper();
MethodTable* pMT = pSimpleWrap->GetMethodTable();
// Retrieve the type of the default interface for the class.
TypeHandle hndDefItfClass;
DefaultInterfaceType DefItfType = GetDefaultInterfaceForClassWrapper(TypeHandle(pMT), &hndDefItfClass);
if ((DefItfType == DefaultInterfaceType_AutoDual) || (DefItfType == DefaultInterfaceType_AutoDispatch))
{
// Make sure we release the BasicIP we're about to get.
SafeComHolder<IUnknown> pBasic = GetBasicIP();
ComMethodTable* pCMT = ComMethodTable::ComMethodTableFromIP(pBasic);
pCMT->CheckParentComVisibility(TRUE);
}
// If the class implements IReflect then use the IDispatchEx implementation.
if (SimpleComCallWrapper::SupportsIReflect(pMT))
{
// The class implements IReflect so lets let it handle IDispatch calls.
// We will do this by exposing the IDispatchEx implementation of IDispatch.
RETURN (IDispatch *)pSimpleWrap->QIStandardInterface(IID_IDispatchEx);
}
// Get the correct default interface
switch (DefItfType)
{
case DefaultInterfaceType_Explicit:
{
_ASSERTE(!hndDefItfClass.IsNull());
_ASSERTE(hndDefItfClass.IsInterface());
CorIfaceAttr ifaceType = hndDefItfClass.GetMethodTable()->GetComInterfaceType();
if (IsDispatchBasedItf(ifaceType))
{
RETURN (IDispatch*)GetComIPFromCCW(this, GUID_NULL, hndDefItfClass.GetMethodTable());
}
else
{
RETURN NULL;
}
}
case DefaultInterfaceType_IUnknown:
{
RETURN NULL;
}
case DefaultInterfaceType_AutoDual:
case DefaultInterfaceType_AutoDispatch:
{
RETURN (IDispatch*)GetBasicIP();
}
case DefaultInterfaceType_BaseComClass:
{
SyncBlock* pBlock = GetSyncBlock();
_ASSERTE(pBlock);
SafeComHolder<IDispatch> pDisp;
RCWHolder pRCW(GetThread());
RCWPROTECT_BEGIN(pRCW, pBlock);
pDisp = pRCW->GetIDispatch();
RCWPROTECT_END(pRCW);
RETURN pDisp.Extract();
}
default:
{
_ASSERTE(!"Invalid default interface type!");
RETURN NULL;
}
}
}
//--------------------------------------------------------------------------
// ComCallable wrapper manager
// constructor
//--------------------------------------------------------------------------
ComCallWrapperCache::ComCallWrapperCache() :
m_cbRef(0),
m_pCacheLineAllocator(NULL),
m_pLoaderAllocator(NULL),
m_lock(CrstCOMWrapperCache)
{
WRAPPER_NO_CONTRACT;
}
//-------------------------------------------------------------------
// ComCallable wrapper manager
// destructor
//-------------------------------------------------------------------
ComCallWrapperCache::~ComCallWrapperCache()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
LOG((LF_INTEROP, LL_INFO100, "ComCallWrapperCache::~ComCallWrapperCache %8.8x in loader allocator [%d] %8.8x\n",
this, GetLoaderAllocator() ? GetLoaderAllocator()->GetCreationNumber() : 0, GetLoaderAllocator()));
if (m_pCacheLineAllocator)
{
delete m_pCacheLineAllocator;
m_pCacheLineAllocator = NULL;
}
LoaderAllocator *pLoaderAllocator = GetLoaderAllocator(); // don't use member directly, need to mask off flags
if (pLoaderAllocator)
{
// clear hook in LoaderAllocator as we're going away
pLoaderAllocator->ResetComCallWrapperCache();
}
}
//-------------------------------------------------------------------
// ComCallable wrapper manager
// Create/Init method
//-------------------------------------------------------------------
ComCallWrapperCache *ComCallWrapperCache::Create(LoaderAllocator *pLoaderAllocator)
{
CONTRACT (ComCallWrapperCache*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(pLoaderAllocator));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
NewHolder<ComCallWrapperCache> pWrapperCache = new ComCallWrapperCache();
LOG((LF_INTEROP, LL_INFO100, "ComCallWrapperCache::Create %8.8x in loader allocator [%d] %8.8x\n",
(ComCallWrapperCache *)pWrapperCache, pLoaderAllocator ? pLoaderAllocator->GetCreationNumber() : 0, pLoaderAllocator));
NewHolder<CCacheLineAllocator> line = new CCacheLineAllocator;
pWrapperCache->m_pLoaderAllocator = pLoaderAllocator;
pWrapperCache->m_pCacheLineAllocator = line;
pWrapperCache->AddRef();
line.SuppressRelease();
pWrapperCache.SuppressRelease();
RETURN pWrapperCache;
}
//-------------------------------------------------------------------
// ComCallable wrapper manager
// LONG AddRef()
//-------------------------------------------------------------------
LONG ComCallWrapperCache::AddRef()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
LONG i = InterlockedIncrement(&m_cbRef);
LOG((LF_INTEROP, LL_INFO100, "ComCallWrapperCache::Addref %8.8x with %d in loader allocator [%d] %8.8x\n",
this, i, GetLoaderAllocator()?GetLoaderAllocator()->GetCreationNumber() : 0, GetLoaderAllocator()));
return i;
}
//-------------------------------------------------------------------
// ComCallable wrapper manager
// LONG Release()
//-------------------------------------------------------------------
LONG ComCallWrapperCache::Release()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
LONG i = InterlockedDecrement(&m_cbRef);
_ASSERTE(i >= 0);
LOG((LF_INTEROP, LL_INFO100, "ComCallWrapperCache::Release %8.8x with %d in loader allocator [%d] %8.8x\n",
this, i, GetLoaderAllocator() ? GetLoaderAllocator()->GetCreationNumber() : 0, GetLoaderAllocator()));
if ( i == 0)
delete this;
return i;
}
//--------------------------------------------------------------------------
// void ComMethodTable::Cleanup()
// free the stubs and the vtable
//--------------------------------------------------------------------------
void ComMethodTable::Cleanup()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
unsigned cbExtraSlots = GetNumExtraSlots(GetInterfaceType());
unsigned cbSlots = m_cbSlots;
SLOT* pComVtable = (SLOT *)(this + 1);
// If we have created and laid out the method desc then we need to delete them.
if (IsLayoutComplete())
{
#ifdef PROFILING_SUPPORTED
// We used to issue the COMClassicVTableDestroyed callback from here.
// However, that causes an AV. At this point the MethodTable is gone
// (as the AppDomain containing it has been unloaded), but the ComMethodTable
// still points to it. The code here used to wrap a TypeHandle around the
// MethodTable pointer, cast to a ClassID, and then call COMClassicVTableDestroyed.
// But the act of casting to a TypeHandle invokes debug-code to verify the
// MethodTable, which causes an AV.
//
// For now, we're not issuing the COMClassicVTableDestroyed callback anymore.
// <REVISIT_TODO>Reexamine the profiling API around
// CCWs and move the callback elsewhere and / or rethink the current
// set of CCW callbacks to mirror reality more accurately.</REVISIT_TODO>
#endif // PROFILING_SUPPORTED
for (unsigned i = cbExtraSlots; i < cbSlots+cbExtraSlots; i++)
{
// Don't bother grabbing the ComCallMethodDesc if the method represented by the
// current vtable slot doesn't belong to the current ComMethodTable.
if (!OwnedbyThisMT(i))
{
continue;
}
// ComCallMethodDescFromSlot returns NULL when the
// ComCallMethodDesc has already been cleaned up.
ComCallMethodDesc* pCMD = ComCallMethodDescFromSlot(i);
if ( (pComVtable[i] == (SLOT)-1 ) ||
(pCMD == NULL)
)
{
continue;
}
// All the stubs that are in a COM->COM+ VTable are to the generic
// helpers (g_pGenericComCallStubFields, etc.). So all we do is
// discard the resources held by the ComMethodDesc.
pCMD->Destruct();
}
}
if (m_pDispatchInfo)
delete m_pDispatchInfo;
if (m_pITypeInfo && !g_fProcessDetach)
SafeRelease(m_pITypeInfo);
// The m_pMDescr and the current instance is allocated from the related LoaderAllocator
// so no cleanup is needed here.
}
//--------------------------------------------------------------------------
// Lay's out the members of a ComMethodTable that represents an IClassX.
//--------------------------------------------------------------------------
void ComMethodTable::LayOutClassMethodTable()
{
CONTRACTL
{
PRECONDITION(m_pMT != NULL);
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
GCX_PREEMP();
unsigned i;
IDispatchVtable* pDispVtable;
SLOT *pComVtable;
unsigned cbPrevSlots = 0;
unsigned cbAlloc = 0;
AllocMemHolder<BYTE> pMDMemoryPtr;
BYTE* pMethodDescMemory = NULL;
size_t writeableOffset = 0;
unsigned cbNumParentVirtualMethods = 0;
unsigned cbTotalParentFields = 0;
unsigned cbParentComMTSlots = 0;
MethodTable* pComPlusParentClass = m_pMT->GetComPlusParentMethodTable();
MethodTable* pParentClass = m_pMT->GetParentMethodTable();
MethodTable* pCurrParentClass = pParentClass;
MethodTable* pCurrMT = m_pMT;
InteropMethodTableData *pCurrParentInteropMT = NULL;
InteropMethodTableData *pCurrInteropMT = NULL;
ComMethodTable* pParentComMT = NULL;
const unsigned cbExtraSlots = GetNumExtraSlots(ifDual);
CQuickEEClassPtrs apClassesToProcess;
int cClassesToProcess = 0;
//
// If we have a parent ensure its IClassX COM method table is laid out.
//
if (pComPlusParentClass)
{
pParentComMT = ComCallWrapperTemplate::SetupComMethodTableForClass(pComPlusParentClass, TRUE);
cbParentComMTSlots = pParentComMT->m_cbSlots;
}
LOG((LF_INTEROP, LL_INFO1000, "LayOutClassMethodTable: %s, parent: %s, this: %p\n", m_pMT->GetDebugClassName(), pParentClass ? pParentClass->GetDebugClassName() : 0, this));
//
// Allocate a temporary space to generate the vtable into.
//
S_UINT32 cbTempVtable = (S_UINT32(m_cbSlots) + S_UINT32(cbExtraSlots)) * S_UINT32(sizeof(SLOT));
if (cbTempVtable.IsOverflow())
ThrowHR(COR_E_OVERFLOW);
NewArrayHolder<BYTE> pTempVtable = new BYTE[cbTempVtable.Value()];
pDispVtable = (IDispatchVtable *)pTempVtable.GetValue();
//
// Set up the IUnknown and IDispatch methods.
//
// Setup IUnknown vtable
pDispVtable->m_qi = (SLOT)Unknown_QueryInterface;
pDispVtable->m_addref = (SLOT)Unknown_AddRef;
pDispVtable->m_release = (SLOT)Unknown_Release;
// Set up the common portion of the IDispatch vtable.
pDispVtable->m_GetTypeInfoCount = (SLOT)Dispatch_GetTypeInfoCount_Wrapper;
pDispVtable->m_GetTypeInfo = (SLOT)Dispatch_GetTypeInfo_Wrapper;
// If the class interface is a pure disp interface then we need to use the
// internal implementation of IDispatch for GetIdsOfNames and Invoke.
if (GetClassInterfaceType() == clsIfAutoDisp)
{
// Use the internal implementation.
pDispVtable->m_GetIDsOfNames = (SLOT)InternalDispatchImpl_GetIDsOfNames_Wrapper;
pDispVtable->m_Invoke = (SLOT)InternalDispatchImpl_Invoke_Wrapper;
}
else
{
// We need to set the entry points to the Dispatch versions which determine
// which implementation to use at runtime based on the class that implements
// the interface.
pDispVtable->m_GetIDsOfNames = (SLOT)Dispatch_GetIDsOfNames_Wrapper;
pDispVtable->m_Invoke = (SLOT)Dispatch_Invoke_Wrapper;
}
//
// Lay out the portion of the vtable containing the methods of the class.
//
// Note that we only do this if the class doesn't have any generic instantiations
// in it's hierarchy.
//
ArrayList NewCOMMethodDescs;
ComCallMethodDescArrayHolder NewCOMMethodDescsHolder(&NewCOMMethodDescs);
unsigned cbNewSlots = 0;
//
// Copy the members down from our parent's template
// We guarantee to have at least all the slots from parent's template
//
pComVtable = ((SLOT*)pDispVtable) + cbExtraSlots;
if (pParentComMT)
{
SLOT *pPrevComVtable = ((SLOT *)(pParentComMT + 1)) + cbExtraSlots;
CopyMemory(pComVtable, pPrevComVtable, sizeof(SLOT) * cbParentComMTSlots);
cbPrevSlots = cbParentComMTSlots;
}
if (!m_pMT->HasGenericClassInstantiationInHierarchy())
{
ExecutableWriterHolderNoLog<BYTE> methodDescMemoryWriteableHolder;
//
// Allocate method desc's for the rest of the slots.
//
unsigned cbMethodDescs = (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc)) * (m_cbSlots - cbParentComMTSlots);
cbAlloc = cbMethodDescs;
if (cbAlloc > 0)
{
pMDMemoryPtr = m_pMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbAlloc + sizeof(UINT_PTR)));
pMethodDescMemory = pMDMemoryPtr;
methodDescMemoryWriteableHolder.AssignExecutableWriterHolder(pMethodDescMemory, cbAlloc + sizeof(UINT_PTR));
writeableOffset = methodDescMemoryWriteableHolder.GetRW() - pMethodDescMemory;
// initialize the method desc memory to zero
FillMemory(pMethodDescMemory + writeableOffset, cbAlloc, 0x0);
*(UINT_PTR *)(pMethodDescMemory + writeableOffset) = cbMethodDescs; // fill in the size of the method desc's
// move past the size
pMethodDescMemory += sizeof(UINT_PTR);
}
_ASSERTE(0 == (((DWORD_PTR)pMethodDescMemory) & (sizeof(void*)-1)));
//
// Create an array of all the classes that need to be laid out.
//
do
{
apClassesToProcess.ReSizeThrows(cClassesToProcess + 2);
apClassesToProcess[cClassesToProcess++] = pCurrMT;
pCurrMT = pCurrMT->GetParentMethodTable();
}
while (pCurrMT != pComPlusParentClass);
apClassesToProcess[cClassesToProcess++] = pCurrMT;
//
// Set up the COM call method desc's for all the methods and fields that were introduced
// between the current class and its parent COM+ class. This includes any methods on
// COM classes.
//
for (cClassesToProcess -= 2; cClassesToProcess >= 0; cClassesToProcess--)
{
//
// Retrieve the current class and the current parent class.
//
pCurrMT = apClassesToProcess[cClassesToProcess];
pCurrInteropMT = pCurrMT->GetComInteropData();
_ASSERTE(pCurrInteropMT);
pCurrParentClass = apClassesToProcess[cClassesToProcess + 1];
//
// Retrieve the number of fields and vtable methods on the parent class.
//
if (pCurrParentClass)
{
cbTotalParentFields = pCurrParentClass->GetNumInstanceFields();
pCurrParentInteropMT = pCurrParentClass->GetComInteropData();
_ASSERTE(pCurrParentInteropMT);
cbNumParentVirtualMethods = pCurrParentInteropMT->cVTable;
}
//
// Set up the COM call method desc's for methods that were not public in the parent class
// but were made public in the current class.
//
for (i = 0; i < cbNumParentVirtualMethods; i++)
{
MethodDesc* pMD = NULL;
InteropMethodTableSlotData *pCurrInteropMD = NULL;
pCurrInteropMD = &pCurrInteropMT->pVTable[i];
pMD = pCurrInteropMD->pMD;
MethodDesc* pParentMD = NULL;
InteropMethodTableSlotData *pCurrParentInteropMD = NULL;
pCurrParentInteropMD = &pCurrParentInteropMT->pVTable[i];
pParentMD = pCurrParentInteropMD->pMD;
if (pMD &&
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
IsOverloadedComVisibleMember(pMD, pParentMD))
{
// some bytes are reserved for CALL xxx before the method desc
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
ComCallMethodDesc* pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
NewCOMMethodDescs.Append(pNewMD);
pNewMDRW->InitMethod(pMD, NULL);
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
FillInComVtableSlot(pComVtable, cbPrevSlots++, pNewMD);
pMethodDescMemory += (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
}
}
//
// Set up the COM call method desc's for all newly introduced public methods.
//
unsigned cbNumVirtualMethods = 0;
cbNumVirtualMethods = pCurrInteropMT->cVTable;
for (i = cbNumParentVirtualMethods; i < cbNumVirtualMethods; i++)
{
MethodDesc* pMD = NULL;
InteropMethodTableSlotData *pCurrInteropMD = NULL;
pCurrInteropMD = &pCurrInteropMT->pVTable[i];
pMD = pCurrInteropMD->pMD;
if (pMD &&
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
IsNewComVisibleMember(pMD))
{
// some bytes are reserved for CALL xxx before the method desc
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
ComCallMethodDesc* pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
NewCOMMethodDescs.Append(pNewMD);
pNewMDRW->InitMethod(pMD, NULL);
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
FillInComVtableSlot(pComVtable, cbPrevSlots++, pNewMD);
pMethodDescMemory += (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
}
}
//
// Add the non virtual methods introduced on the current class.
//
MethodTable::MethodIterator it(pCurrMT);
for (; it.IsValid(); it.Next())
{
if (!it.IsVirtual()) {
MethodDesc* pMD = it.GetMethodDesc();
if (pMD != NULL && !IsDuplicateClassItfMD(pMD, it.GetSlotNumber()) &&
IsNewComVisibleMember(pMD) && !pMD->IsStatic() && !pMD->IsCtor()
&& (!pCurrMT->IsValueType() || (GetClassInterfaceType() != clsIfAutoDual && IsStrictlyUnboxed(pMD))))
{
// some bytes are reserved for CALL xxx before the method desc
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
ComCallMethodDesc* pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
NewCOMMethodDescs.Append(pNewMD);
pNewMDRW->InitMethod(pMD, NULL);
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
FillInComVtableSlot(pComVtable, cbPrevSlots++, pNewMD);
pMethodDescMemory += (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
}
}
}
//
// Set up the COM call method desc's for the public fields defined in the current class.
//
// <TODO>check this approximation - we may be losing exact type information </TODO>
ApproxFieldDescIterator fdIterator(pCurrMT, ApproxFieldDescIterator::INSTANCE_FIELDS);
FieldDesc* pFD = NULL;
while ((pFD = fdIterator.Next()) != NULL)
{
if (IsMemberVisibleFromCom(pCurrMT, pFD->GetMemberDef(), mdTokenNil)) // if it is a public field grab it
{
// set up a getter method
// some bytes are reserved for CALL xxx before the method desc
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
ComCallMethodDesc* pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
NewCOMMethodDescs.Append(pNewMD);
pNewMDRW->InitField(pFD, TRUE);
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
FillInComVtableSlot(pComVtable, cbPrevSlots++, pNewMD);
pMethodDescMemory+= (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
// setup a setter method
// some bytes are reserved for CALL xxx before the method desc
pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
NewCOMMethodDescs.Append(pNewMD);
pNewMDRW->InitField(pFD, FALSE);
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
FillInComVtableSlot(pComVtable, cbPrevSlots++, pNewMD);
pMethodDescMemory+= (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
}
}
}
}
_ASSERTE(m_cbSlots == cbPrevSlots);
{
// Take the lock and copy data from the temporary vtable to this instance
CrstHolder ch(&g_CreateWrapperTemplateCrst);
if (IsLayoutComplete())
return;
ExecutableWriterHolder<ComMethodTable> comMTWriterHolder(this, sizeof(ComMethodTable) + cbTempVtable.Value());
// IDispatch vtable follows the header
CopyMemory(comMTWriterHolder.GetRW() + 1, pDispVtable, cbTempVtable.Value());
// Set the layout complete flag and release the lock.
comMTWriterHolder.GetRW()->m_Flags |= enum_LayoutComplete;
// We've successfully laid out the class method table so we need to suppress the release of the
// memory for the ComCallMethodDescs and store it inside the ComMethodTable so we can
// release it when we clean up the ComMethodTable.
comMTWriterHolder.GetRW()->m_pMDescr = (BYTE*)pMDMemoryPtr;
pMDMemoryPtr.SuppressRelease();
NewCOMMethodDescsHolder.SuppressRelease();
}
LOG((LF_INTEROP, LL_INFO1000, "LayOutClassMethodTable: %s, parent: %s, this: %p [DONE]\n", m_pMT->GetDebugClassName(), pParentClass ? pParentClass->GetDebugClassName() : 0, this));
}
//--------------------------------------------------------------------------
// Lay out the members of a ComMethodTable that represents an interface.
//--------------------------------------------------------------------------
BOOL ComMethodTable::LayOutInterfaceMethodTable(MethodTable* pClsMT)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pClsMT, NULL_OK));
PRECONDITION(pClsMT == NULL || !pClsMT->IsInterface());
}
CONTRACTL_END;
GCX_PREEMP();
MethodTable *pItfClass = m_pMT;
CorIfaceAttr ItfType = m_pMT->GetComInterfaceType();
ULONG cbExtraSlots = GetNumExtraSlots(ItfType);
BYTE *pMethodDescMemory = NULL;
IUnkVtable* pUnkVtable;
SLOT *pComVtable;
unsigned i;
LOG((LF_INTEROP, LL_INFO1000, "LayOutInterfaceMethodTable: %s, this: %p\n", pItfClass->GetDebugClassName(), this));
unsigned cbSlots = pItfClass->GetNumVirtuals();
//
// Allocate a temporary space to generate the vtable into.
//
S_UINT32 cbTempVtable = (S_UINT32(m_cbSlots) + S_UINT32(cbExtraSlots)) * S_UINT32(sizeof(SLOT));
cbTempVtable += S_UINT32(cbSlots) * S_UINT32((COMMETHOD_PREPAD + sizeof(ComCallMethodDesc)));
if (cbTempVtable.IsOverflow())
ThrowHR(COR_E_OVERFLOW);
NewArrayHolder<BYTE> pTempVtable = new BYTE[cbTempVtable.Value()];
pUnkVtable = (IUnkVtable *)pTempVtable.GetValue();
pComVtable = ((SLOT*)pUnkVtable) + cbExtraSlots;
// Set all vtable slots to -1 for sparse vtables. That way we catch attempts
// to access empty slots quickly and, during cleanup, we can tell empty
// slots from full ones.
if (m_pMT->IsSparseForCOMInterop())
memset(pUnkVtable + cbExtraSlots, -1, m_cbSlots * sizeof(SLOT));
// Method descs are at the end of the vtable
// m_cbSlots interfaces methods + IUnk methods
pMethodDescMemory = (BYTE *)&pComVtable[m_cbSlots];
// Setup IUnk vtable
pUnkVtable->m_qi = (SLOT)Unknown_QueryInterface;
pUnkVtable->m_addref = (SLOT)Unknown_AddRef;
pUnkVtable->m_release = (SLOT)Unknown_Release;
if (IsDispatchBasedItf(ItfType))
{
// Setup the IDispatch vtable.
IDispatchVtable* pDispVtable = (IDispatchVtable*)pUnkVtable;
// Set up the common portion of the IDispatch vtable.
pDispVtable->m_GetTypeInfoCount = (SLOT)Dispatch_GetTypeInfoCount_Wrapper;
pDispVtable->m_GetTypeInfo = (SLOT)Dispatch_GetTypeInfo_Wrapper;
// If the interface is a pure disp interface then we need to use the internal
// implementation since OleAut does not support invoking on pure disp interfaces.
if (ItfType == ifDispatch)
{
// Use the internal implementation.
pDispVtable->m_GetIDsOfNames = (SLOT)InternalDispatchImpl_GetIDsOfNames_Wrapper;
pDispVtable->m_Invoke = (SLOT)InternalDispatchImpl_Invoke_Wrapper;
}
else
{
// We need to set the entry points to the Dispatch versions which determine
// which implmentation to use at runtime based on the class that implements
// the interface.
pDispVtable->m_GetIDsOfNames = (SLOT)Dispatch_GetIDsOfNames_Wrapper;
pDispVtable->m_Invoke = (SLOT)Dispatch_Invoke_Wrapper;
}
}
ArrayList NewCOMMethodDescs;
ComCallMethodDescArrayHolder NewCOMMethodDescsHolder(&NewCOMMethodDescs);
for (i = 0; i < cbSlots; i++)
{
// Some space for a CALL xx xx xx xx stub is reserved before the beginning of the MethodDesc
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
NewCOMMethodDescs.Append(pNewMD);
MethodDesc* pIntfMD = m_pMT->GetMethodDescForSlot(i);
if (m_pMT->HasInstantiation())
{
pIntfMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
pIntfMD,
m_pMT,
FALSE, // forceBoxedEntryPoint
Instantiation(), // methodInst
FALSE, // allowInstParam
TRUE); // forceRemotableMethod
}
MethodDesc *pClassMD = NULL;
if (pClsMT != NULL)
{
DispatchSlot impl(pClsMT->FindDispatchSlotForInterfaceMD(pIntfMD, FALSE /* throwOnConflict */));
pClassMD = impl.GetMethodDesc();
}
if (pClassMD != NULL)
{
pNewMD->InitMethod(pClassMD, pIntfMD);
}
else
{
// we will perform interface dispatch at run-time
pNewMD->InitMethod(pIntfMD, NULL);
}
pMethodDescMemory += (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
}
{
// Take the lock and copy data from the temporary vtable to this instance
CrstHolder ch(&g_CreateWrapperTemplateCrst);
if (IsLayoutComplete())
return TRUE;
ExecutableWriterHolder<ComMethodTable> comMTWriterHolder(this, sizeof(ComMethodTable) + cbTempVtable.Value());
size_t writeableOffset = (BYTE*)comMTWriterHolder.GetRW() - (BYTE*)this;
// IUnk vtable follows the header
CopyMemory(comMTWriterHolder.GetRW() + 1, pUnkVtable, cbTempVtable.Value());
// Finish by emitting stubs and initializing the slots
pUnkVtable = (IUnkVtable *)(this + 1);
pComVtable = ((SLOT*)pUnkVtable) + cbExtraSlots;
SLOT *pComVtableRW = (SLOT*)((BYTE*)pComVtable + writeableOffset);
// Method descs are at the end of the vtable
// m_cbSlots interfaces methods + IUnk methods
pMethodDescMemory = (BYTE *)&pComVtable[m_cbSlots];
for (i = 0; i < cbSlots; i++)
{
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
ComCallMethodDesc* pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
MethodDesc* pIntfMD = m_pMT->GetMethodDescForSlot(i);
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
UINT slotIndex = (pIntfMD->GetComSlot() - cbExtraSlots);
FillInComVtableSlot(pComVtableRW, slotIndex, pNewMD);
pMethodDescMemory += (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
}
// Set the layout complete flag and release the lock.
comMTWriterHolder.GetRW()->m_Flags |= enum_LayoutComplete;
NewCOMMethodDescsHolder.SuppressRelease();
}
#ifdef PROFILING_SUPPORTED
// Notify profiler of the CCW, so it can avoid double-counting.
{
BEGIN_PROFILER_CALLBACK(CORProfilerTrackCCW());
#if defined(_DEBUG)
WCHAR rIID[40]; // {00000000-0000-0000-0000-000000000000}
GuidToLPWSTR(m_IID, rIID, ARRAY_SIZE(rIID));
LOG((LF_CORPROF, LL_INFO100, "COMClassicVTableCreated Class:%hs, IID:%ls, vTbl:%#08x\n",
pItfClass->GetDebugClassName(), rIID, pUnkVtable));
#else
LOG((LF_CORPROF, LL_INFO100, "COMClassicVTableCreated Class:%#x, IID:{%08x-...}, vTbl:%#08x\n",
pItfClass, m_IID.Data1, pUnkVtable));
#endif
(&g_profControlBlock)->COMClassicVTableCreated((ClassID) TypeHandle(pItfClass).AsPtr(),
m_IID,
pUnkVtable,
m_cbSlots+cbExtraSlots);
END_PROFILER_CALLBACK();
}
#endif // PROFILING_SUPPORTED
LOG((LF_INTEROP, LL_INFO1000, "LayOutInterfaceMethodTable: %s, this: %p [DONE]\n", pItfClass->GetDebugClassName(), this));
return TRUE;
}
void ComMethodTable::LayOutBasicMethodTable()
{
CONTRACTL
{
PRECONDITION(m_pMT != NULL);
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;
IDispatchVtable* pDispVtable;
LOG((LF_INTEROP, LL_INFO1000, "LayOutBasicMethodTable: %s, this: %p\n", m_pMT->GetDebugClassName(), this));
//
// Set up the IUnknown and IDispatch methods. Each thread will write exactly the same values to the
// slots so we let it run concurrently and execute a memory barrier by doing InterlockOr at the end.
//
// IDispatch vtable follows the header
pDispVtable = (IDispatchVtable*)(this + 1);
// Setup IUnknown vtable
pDispVtable->m_qi = (SLOT)Unknown_QueryInterface;
pDispVtable->m_addref = (SLOT)Unknown_AddRef;
pDispVtable->m_release = (SLOT)Unknown_Release;
// Set up the common portion of the IDispatch vtable.
pDispVtable->m_GetTypeInfoCount = (SLOT)Dispatch_GetTypeInfoCount_Wrapper;
pDispVtable->m_GetTypeInfo = (SLOT)Dispatch_GetTypeInfo_Wrapper;
// If the class interface is a pure disp interface then we need to use the
// internal implementation of IDispatch for GetIdsOfNames and Invoke.
if (GetClassInterfaceType() == clsIfAutoDisp)
{
// Use the internal implementation.
pDispVtable->m_GetIDsOfNames = (SLOT)InternalDispatchImpl_GetIDsOfNames_Wrapper;
pDispVtable->m_Invoke = (SLOT)InternalDispatchImpl_Invoke_Wrapper;
}
else
{
// We need to set the entry points to the Dispatch versions which determine
// which implementation to use at runtime based on the class that implements
// the interface.
pDispVtable->m_GetIDsOfNames = (SLOT)Dispatch_GetIDsOfNames_Wrapper;
pDispVtable->m_Invoke = (SLOT)Dispatch_Invoke_Wrapper;
}
//
// Set the layout complete flag.
//
InterlockedOr((LONG*)&m_Flags, enum_LayoutComplete);
LOG((LF_INTEROP, LL_INFO1000, "LayOutClassMethodTable: %s, this: %p [DONE]\n", m_pMT->GetDebugClassName(), this));
}
//--------------------------------------------------------------------------
// Retrieves the DispatchInfo associated with the COM method table. If
// the DispatchInfo has not been initialized yet then it is initilized.
//--------------------------------------------------------------------------
DispatchInfo *ComMethodTable::GetDispatchInfo()
{
CONTRACT (DispatchInfo*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
if (!m_pDispatchInfo)
{
// Create the DispatchInfo object.
NewHolder<DispatchInfo> pDispInfo = new DispatchInfo(m_pMT);
// Synchronize the DispatchInfo with the actual object.
pDispInfo->SynchWithManagedView();
ExecutableWriterHolder<ComMethodTable> comMTWriterHolder(this, sizeof(ComMethodTable));
// Swap the lock into the class member in a thread safe manner.
if (NULL == InterlockedCompareExchangeT(&comMTWriterHolder.GetRW()->m_pDispatchInfo, pDispInfo.GetValue(), NULL))
pDispInfo.SuppressRelease();
}
RETURN m_pDispatchInfo;
}
//--------------------------------------------------------------------------
// Set an ITypeInfo pointer for the method table.
//--------------------------------------------------------------------------
void ComMethodTable::SetITypeInfo(ITypeInfo *pNew)
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pNew));
}
CONTRACTL_END;
if (InterlockedCompareExchangeT(&m_pITypeInfo, pNew, NULL) == NULL)
{
SafeAddRef(pNew);
}
}
//--------------------------------------------------------------------------
// Return the parent ComMethodTable.
//--------------------------------------------------------------------------
ComMethodTable *ComMethodTable::GetParentClassComMT()
{
CONTRACT (ComMethodTable*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(IsIClassX());
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
MethodTable *pParentComPlusMT = m_pMT->GetComPlusParentMethodTable();
if (!pParentComPlusMT)
RETURN NULL;
ComCallWrapperTemplate *pTemplate = pParentComPlusMT->GetComCallWrapperTemplate();
if (!pTemplate)
RETURN NULL;
RETURN pTemplate->GetClassComMT();
}
//---------------------------------------------------------
// ComCallWrapperTemplate::CCWInterfaceMapIterator
//---------------------------------------------------------
ComCallWrapperTemplate::CCWInterfaceMapIterator::CCWInterfaceMapIterator(TypeHandle thClass)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
CAN_TAKE_LOCK;
}
CONTRACTL_END;
MethodTable *pMT = thClass.GetMethodTable();
// iterate interface map of the type
MethodTable::InterfaceMapIterator it = pMT->IterateInterfaceMap();
while (it.Next())
{
MethodTable *pItfMT = it.GetInterface(pMT);
AppendInterface(pItfMT);
}
Reset();
}
// Append a new interface to the m_Interfaces array.
ComCallWrapperTemplate::CCWInterfaceMapIterator::InterfaceProps &ComCallWrapperTemplate::CCWInterfaceMapIterator::AppendInterface(MethodTable *pItfMT)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
InterfaceProps &props = *m_Interfaces.Append();
props.m_pItfMT = pItfMT;
return props;
}
//---------------------------------------------------------
// One-time init
//---------------------------------------------------------
/*static*/
void ComCallWrapperTemplate::Init()
{
WRAPPER_NO_CONTRACT;
g_CreateWrapperTemplateCrst.Init(CrstWrapperTemplate, (CrstFlags)(CRST_REENTRANCY | CRST_HOST_BREAKABLE));
}
ComCallWrapperTemplate::ComCallWrapperTemplate()
{
LIMITED_METHOD_CONTRACT;
}
//--------------------------------------------------------------------------
// static void ComCallWrapperTemplate::Cleanup(ComCallWrapperTemplate* pTemplate)
// Cleanup the template
//--------------------------------------------------------------------------
void ComCallWrapperTemplate::Cleanup()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
INSTANCE_CHECK;
PRECONDITION(!m_thClass.IsNull());
}
CONTRACTL_END;
for (unsigned j = 0; j < m_cbInterfaces; j++)
{
SLOT* pComVtable = m_rgpIPtr[j];
if (pComVtable != 0)
{
ComMethodTable* pHeader = (ComMethodTable*)pComVtable-1;
pHeader->Release(); // release the vtable
}
#ifdef _DEBUG
m_rgpIPtr[j] = (SLOT *)(size_t)INVALID_POINTER_CD;
#endif
}
if (m_pClassComMT)
m_pClassComMT->Release();
if (m_pBasicComMT)
m_pBasicComMT->Release();
delete[] (BYTE*)this;
}
LONG ComCallWrapperTemplate::AddRef()
{
WRAPPER_NO_CONTRACT;
return InterlockedIncrement(&m_cbRefCount);
}
LONG ComCallWrapperTemplate::Release()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(m_cbRefCount > 0);
}
CONTRACTL_END;
// use a different var here becuase cleanup will delete the object
// so can no longer make member refs
LONG cbRef = InterlockedDecrement(&m_cbRefCount);
if (cbRef == 0)
Cleanup();
return cbRef;
}
ComMethodTable* ComCallWrapperTemplate::GetClassComMT()
{
CONTRACT (ComMethodTable*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(SupportsIClassX());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
// First check the cache
if (m_pClassComMT)
RETURN m_pClassComMT;
MethodTable *pMT = m_thClass.GetMethodTable();
// We haven't set it up yet, generate one.
ComMethodTable* pClassComMT = CreateComMethodTableForClass(pMT);
pClassComMT->AddRef();
// Cache it.
if (InterlockedCompareExchangeT(&m_pClassComMT, pClassComMT, NULL) != NULL)
{
pClassComMT->Release();
}
RETURN m_pClassComMT;
}
ComMethodTable* ComCallWrapperTemplate::GetComMTForItf(MethodTable *pItfMT)
{
CONTRACT (ComMethodTable*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pItfMT));
PRECONDITION(pItfMT->IsInterface());
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
// Look through all the implemented interfaces to see if the specified
// one is present yet.
for (UINT iItf = 0; iItf < m_cbInterfaces; iItf++)
{
ComMethodTable* pItfComMT = (ComMethodTable *)m_rgpIPtr[iItf] - 1;
if (pItfComMT && (pItfComMT->m_pMT == pItfMT))
RETURN pItfComMT;
}
// The class does not implement the specified interface.
RETURN NULL;
}
ComMethodTable* ComCallWrapperTemplate::GetBasicComMT()
{
CONTRACT (ComMethodTable*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
RETURN m_pBasicComMT;
}
ULONG ComCallWrapperTemplate::GetNumInterfaces()
{
LIMITED_METHOD_CONTRACT;
return m_cbInterfaces;
}
SLOT* ComCallWrapperTemplate::GetVTableSlot(ULONG index)
{
CONTRACT (SLOT*)
{
WRAPPER(THROWS);
WRAPPER(GC_TRIGGERS);
MODE_ANY;
PRECONDITION(index >= 0 && index < m_cbInterfaces);
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
RETURN m_rgpIPtr[index];
}
// Determines whether the template is for a type that cannot be safely marshalled to
// an out of proc COM client
BOOL ComCallWrapperTemplate::IsSafeTypeForMarshalling()
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_PREEMPTIVE;
}
CONTRACTL_END;
if (m_flags & enum_IsSafeTypeForMarshalling)
{
return TRUE;
}
BOOL isSafe = TRUE;
PTR_MethodTable pMt = this->GetClassType().GetMethodTable();
EX_TRY
{
// Do casting checks so that we handle derived types as well. The base blocked types are:
// System.Reflection.Assembly, System.Reflection.MemberInfo, System.Reflection.Module,
// System.Reflection.MethodBody, and System.Reflection.ParameterInfo.
// Some interesting derived types that get blocked as a result are:
// System.Type, System.Reflection.TypeInfo, System.Reflection.MethodInfo, and System.Reflection.FieldInfo
if (pMt->CanCastToClass(CoreLibBinder::GetClass(CLASS__ASSEMBLYBASE)) ||
pMt->CanCastToClass(CoreLibBinder::GetClass(CLASS__MEMBER)) ||
pMt->CanCastToClass(CoreLibBinder::GetClass(CLASS__MODULEBASE)) ||
pMt->CanCastToClass(CoreLibBinder::GetClass(CLASS__RUNTIME_METHOD_BODY)) ||
pMt->CanCastToClass(CoreLibBinder::GetClass(CLASS__PARAMETER)))
{
isSafe = FALSE;
}
}
EX_CATCH
{
isSafe = FALSE;
}
EX_END_CATCH(SwallowAllExceptions);
if (isSafe)
{
InterlockedOr((LONG*)&m_flags, enum_IsSafeTypeForMarshalling);
}
return isSafe;
}
//--------------------------------------------------------------------------
// Checks to see if the parent of the current class interface is visible to COM.
// Throws an InvalidOperationException if not.
//--------------------------------------------------------------------------
void ComCallWrapperTemplate::CheckParentComVisibility(BOOL fForIDispatch)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
// Throw an exception to report the error.
if (!CheckParentComVisibilityNoThrow(fForIDispatch))
{
ComCallWrapperTemplate *invisParent = FindInvisibleParent();
_ASSERTE(invisParent != NULL);
SString thisType;
SString invisParentType;
TypeString::AppendType(thisType, m_thClass);
TypeString::AppendType(invisParentType, invisParent->m_thClass);
COMPlusThrow(kInvalidOperationException, IDS_EE_COM_INVISIBLE_PARENT, thisType.GetUnicode(), invisParentType.GetUnicode());
}
}
BOOL ComCallWrapperTemplate::CheckParentComVisibilityNoThrow(BOOL fForIDispatch)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
// If the parent is visible to COM then everything is ok.
if (!HasInvisibleParent())
return TRUE;
return FALSE;
}
DefaultInterfaceType ComCallWrapperTemplate::GetDefaultInterface(MethodTable **ppDefaultItf)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
if ((m_flags & enum_DefaultInterfaceTypeComputed) == 0)
{
// we have not computed the default interface yet
TypeHandle th;
DefaultInterfaceType defItfType = GetDefaultInterfaceForClassInternal(m_thClass, &th);
_ASSERTE(th.IsNull() || !th.IsTypeDesc());
m_pDefaultItf = th.AsMethodTable();
InterlockedOr((LONG*)&m_flags, enum_DefaultInterfaceTypeComputed | (DWORD)defItfType);
}
*ppDefaultItf = m_pDefaultItf;
return (DefaultInterfaceType)(m_flags & enum_DefaultInterfaceTypeMask);
}
//--------------------------------------------------------------------------
// Creates a ComMethodTable for a class's IClassX.
//--------------------------------------------------------------------------
ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForClass(MethodTable *pClassMT)
{
CONTRACT (ComMethodTable*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(pClassMT));
PRECONDITION(!pClassMT->IsInterface());
PRECONDITION(!pClassMT->GetComPlusParentMethodTable() || pClassMT->GetComPlusParentMethodTable()->GetComCallWrapperTemplate());
PRECONDITION(SupportsIClassX());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
unsigned cbNewPublicFields = 0;
unsigned cbNewPublicMethods = 0;
MethodTable* pComPlusParentClass = pClassMT->GetComPlusParentMethodTable();
MethodTable* pParentClass = pClassMT->GetParentMethodTable();
MethodTable* pCurrParentClass = pComPlusParentClass;
MethodTable* pCurrMT = pClassMT;
InteropMethodTableData* pCurrParentInteropMT = NULL;
InteropMethodTableData* pCurrInteropMT = NULL;
CorClassIfaceAttr ClassItfType = pClassMT->GetComClassInterfaceType();
ComMethodTable *pParentComMT = NULL;
unsigned cbTotalParentFields = 0;
unsigned cbNumParentVirtualMethods = 0;
unsigned cbParentComMTSlots = 0;
unsigned i;
const unsigned cbExtraSlots = ComMethodTable::GetNumExtraSlots(ifDual);
CQuickEEClassPtrs apClassesToProcess;
int cClassesToProcess = 0;
// If the specified class has a parent then retrieve information on him.
// This makes sure we always have the space for parent slots
if (pComPlusParentClass)
{
ComCallWrapperTemplate *pComPlusParentTemplate = pComPlusParentClass->GetComCallWrapperTemplate();
_ASSERTE(pComPlusParentTemplate);
pParentComMT = pComPlusParentTemplate->GetClassComMT();
cbParentComMTSlots = pParentComMT->GetNumSlots();
}
// We only set up the members of the class interface if the doesn't have any generic instantiations
// in it's hierarchy.
if (!pClassMT->HasGenericClassInstantiationInHierarchy())
{
LOG((LF_INTEROP, LL_INFO1000, "CreateComMethodTableForClass %s\n", pClassMT->GetClass()->GetDebugClassName()));
LOG((LF_INTEROP, LL_INFO1000, "parent class: %s\n", (pComPlusParentClass) ? pParentComMT->GetMethodTable()->GetClass()->GetDebugClassName() : 0));
// Create an array of all the classes for which we need to compute the added members.
do
{
apClassesToProcess.ReSizeThrows(cClassesToProcess + 2);
apClassesToProcess[cClassesToProcess++] = pCurrMT;
pCurrMT = pCurrMT->GetParentMethodTable();
}
while (pCurrMT != pComPlusParentClass);
apClassesToProcess[cClassesToProcess++] = pCurrMT;
// Compute the number of methods and fields that were added between our parent
// COM+ class and the current class. This includes methods on COM classes
// between the current class and its parent COM+ class.
for (cClassesToProcess -= 2; cClassesToProcess >= 0; cClassesToProcess--)
{
// Retrieve the current class and the current parent class.
pCurrMT = apClassesToProcess[cClassesToProcess];
pCurrInteropMT = pCurrMT->GetComInteropData();
_ASSERTE(pCurrInteropMT);
pCurrParentClass = apClassesToProcess[cClassesToProcess + 1];
// Retrieve the number of fields and vtable methods on the parent class.
if (pCurrParentClass)
{
cbTotalParentFields = pCurrParentClass->GetNumInstanceFields();
pCurrParentInteropMT = pCurrParentClass->GetComInteropData();
_ASSERTE(pCurrParentInteropMT);
cbNumParentVirtualMethods = pCurrParentInteropMT->cVTable;
}
// Compute the number of methods that were private but made public on this class.
for (i = 0; i < cbNumParentVirtualMethods; i++)
{
MethodDesc* pMD = NULL;
InteropMethodTableSlotData *pCurrInteropMD = NULL;
pCurrInteropMD = &pCurrInteropMT->pVTable[i];
pMD = pCurrInteropMD->pMD;
MethodDesc* pParentMD = NULL;
InteropMethodTableSlotData *pCurrParentInteropMD = NULL;
pCurrParentInteropMD = &pCurrParentInteropMT->pVTable[i];
pParentMD = pCurrParentInteropMD->pMD;
if (pMD &&
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
IsOverloadedComVisibleMember(pMD, pParentMD))
{
cbNewPublicMethods++;
}
}
// Compute the number of public methods that were added.
unsigned cbNumVirtualMethods = 0;
cbNumVirtualMethods = pCurrInteropMT->cVTable;
for (i = cbNumParentVirtualMethods; i < cbNumVirtualMethods; i++)
{
MethodDesc* pMD = NULL;
InteropMethodTableSlotData *pCurrInteropMD = NULL;
pCurrInteropMD = &pCurrInteropMT->pVTable[i];
pMD = pCurrInteropMD->pMD;
if (pMD &&
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
IsNewComVisibleMember(pMD))
{
cbNewPublicMethods++;
}
}
// Add the non virtual methods introduced on the current class.
MethodTable::MethodIterator it(pCurrMT);
for (; it.IsValid(); it.Next())
{
if (!it.IsVirtual())
{
MethodDesc* pMD = it.GetMethodDesc();
if (pMD && !IsDuplicateClassItfMD(pMD, it.GetSlotNumber()) && IsNewComVisibleMember(pMD) &&
!pMD->IsStatic() && !pMD->IsCtor() &&
(!pCurrMT->IsValueType() || (ClassItfType != clsIfAutoDual && IsStrictlyUnboxed(pMD))))
{
cbNewPublicMethods++;
}
}
}
// Compute the number of new public fields this class introduces.
// <TODO>check this approximation </TODO>
ApproxFieldDescIterator fdIterator(pCurrMT, ApproxFieldDescIterator::INSTANCE_FIELDS);
FieldDesc* pFD;
while ((pFD = fdIterator.Next()) != NULL)
{
if (IsMemberVisibleFromCom(pCurrMT, pFD->GetMemberDef(), mdTokenNil))
cbNewPublicFields++;
}
}
}
// Alloc space for the class method table, includes getter and setter
// for public fields
S_UINT32 cbNewSlots = S_UINT32(cbNewPublicFields) * S_UINT32(2) + S_UINT32(cbNewPublicMethods);
S_UINT32 cbTotalSlots = S_UINT32(cbParentComMTSlots) + cbNewSlots;
LOG((LF_INTEROP, LL_INFO1000, "cbExtraSlots: %d\n", cbExtraSlots));
LOG((LF_INTEROP, LL_INFO1000, "cbParentComMTSlots: %d\n", cbParentComMTSlots));
LOG((LF_INTEROP, LL_INFO1000, "cbNewSlots: %d\n", cbNewSlots.IsOverflow() ? 0 : cbNewSlots.Value()));
LOG((LF_INTEROP, LL_INFO1000, " cbNewPublicFields: %d\n", cbNewPublicFields));
LOG((LF_INTEROP, LL_INFO1000, " cbNewPublicMethods: %d\n", cbNewPublicMethods));
LOG((LF_INTEROP, LL_INFO1000, "cbTotalSlots: %d\n", cbTotalSlots.IsOverflow() ? 0 : cbTotalSlots.Value()));
// Alloc COM vtable & method descs
S_UINT32 cbVtable = (cbTotalSlots + S_UINT32(cbExtraSlots)) * S_UINT32(sizeof(SLOT));
S_UINT32 cbToAlloc = S_UINT32(sizeof(ComMethodTable)) + cbVtable;
if (cbToAlloc.IsOverflow())
ThrowHR(COR_E_OVERFLOW);
AllocMemHolder<ComMethodTable> pComMT(pClassMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbToAlloc.Value())));
_ASSERTE(!cbNewSlots.IsOverflow() && !cbTotalSlots.IsOverflow() && !cbVtable.IsOverflow());
ExecutableWriterHolder<ComMethodTable> comMTWriterHolder(pComMT, cbToAlloc.Value());
ComMethodTable* pComMTRW = comMTWriterHolder.GetRW();
// set up the header
pComMTRW->m_ptReserved = (SLOT)(size_t)0xDEADC0FF; // reserved
pComMTRW->m_pMT = pClassMT; // pointer to the class method table
pComMTRW->m_cbRefCount = 0;
pComMTRW->m_pMDescr = NULL;
pComMTRW->m_pITypeInfo = NULL;
pComMTRW->m_pDispatchInfo = NULL;
pComMTRW->m_cbSlots = cbTotalSlots.Value(); // number of slots not counting IDisp methods.
pComMTRW->m_IID = GUID_NULL;
// Set the flags.
pComMTRW->m_Flags = enum_ClassVtableMask | ClassItfType;
// Determine if the interface is visible from COM.
if (IsTypeVisibleFromCom(TypeHandle(pComMT->m_pMT)))
pComMTRW->m_Flags |= enum_ComVisible;
#if _DEBUG
{
// In debug set all the vtable slots to 0xDEADCA11.
SLOT *pComVTable = (SLOT*)(pComMTRW + 1);
for (unsigned iComSlots = 0; iComSlots < cbTotalSlots.Value() + cbExtraSlots; iComSlots++)
*(pComVTable + iComSlots) = (SLOT)(size_t)0xDEADCA11;
}
#endif
LOG((LF_INTEROP, LL_INFO1000, "---------- end of CreateComMethodTableForClass %s -----------\n", pClassMT->GetClass()->GetDebugClassName()));
pComMT.SuppressRelease();
RETURN pComMT;
}
//--------------------------------------------------------------------------
// Creates a ComMethodTable for a an interface.
//--------------------------------------------------------------------------
ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForInterface(MethodTable* pInterfaceMT)
{
CONTRACT (ComMethodTable*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(pInterfaceMT));
PRECONDITION(pInterfaceMT->IsInterface());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
MethodTable *pItfClass = pInterfaceMT;
CorIfaceAttr ItfType = pInterfaceMT->GetComInterfaceType();
ULONG cbExtraSlots = ComMethodTable::GetNumExtraSlots(ItfType);
LOG((LF_INTEROP, LL_INFO1000, "CreateComMethodTableForInterface %s\n", pItfClass->GetDebugClassName()));
// @todo get slots off the methodtable
unsigned cbSlots = pInterfaceMT->GetNumVirtuals();
unsigned cbComSlots = pInterfaceMT->IsSparseForCOMInterop() ? pInterfaceMT->GetClass()->GetSparseCOMInteropVTableMap()->GetNumVTableSlots() : cbSlots;
LOG((LF_INTEROP, LL_INFO1000, "cbExtraSlots = %d\n", cbExtraSlots));
LOG((LF_INTEROP, LL_INFO1000, "cbComSlots = %d\n", cbComSlots));
LOG((LF_INTEROP, LL_INFO1000, "cbSlots = %d\n", cbSlots));
S_UINT32 cbVtable = (S_UINT32(cbComSlots) + S_UINT32(cbExtraSlots)) * S_UINT32(sizeof(SLOT));
S_UINT32 cbMethDescs = S_UINT32(cbSlots) * S_UINT32((COMMETHOD_PREPAD + sizeof(ComCallMethodDesc)));
S_UINT32 cbToAlloc = S_UINT32(sizeof(ComMethodTable)) + cbVtable + cbMethDescs;
if (cbToAlloc.IsOverflow())
ThrowHR(COR_E_OVERFLOW);
AllocMemHolder<ComMethodTable> pComMT(pInterfaceMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbToAlloc.Value())));
_ASSERTE(!cbVtable.IsOverflow() && !cbMethDescs.IsOverflow());
ExecutableWriterHolder<ComMethodTable> comMTWriterHolder(pComMT, cbToAlloc.Value());
ComMethodTable* pComMTRW = comMTWriterHolder.GetRW();
// set up the header
pComMTRW->m_ptReserved = (SLOT)(size_t)0xDEADC0FF; // reserved
pComMTRW->m_pMT = pInterfaceMT; // pointer to the interface's method table
pComMTRW->m_cbSlots = cbComSlots; // number of slots not counting IUnk
pComMTRW->m_cbRefCount = 0;
pComMTRW->m_pMDescr = NULL;
pComMTRW->m_pITypeInfo = NULL;
pComMTRW->m_pDispatchInfo = NULL;
// Set the flags.
pComMTRW->m_Flags = ItfType;
// Set the IID of the interface.
pInterfaceMT->GetGuid(&pComMTRW->m_IID, TRUE);
pComMTRW->m_Flags |= enum_GuidGenerated;
// Determine if the interface is visible from COM.
if (IsTypeVisibleFromCom(TypeHandle(pComMT->m_pMT)))
pComMTRW->m_Flags |= enum_ComVisible;
// Determine if the interface is a COM imported class interface.
if (pItfClass->GetClass()->IsComClassInterface())
pComMTRW->m_Flags |= enum_ComClassItf;
#ifdef _DEBUG
{
// In debug set all the vtable slots to 0xDEADCA11.
SLOT *pComVTable = (SLOT*)(pComMTRW + 1);
for (unsigned iComSlots = 0; iComSlots < cbComSlots + cbExtraSlots; iComSlots++)
*(pComVTable + iComSlots) = (SLOT)(size_t)0xDEADCA11;
}
#endif
LOG((LF_INTEROP, LL_INFO1000, "---------- end of CreateComMethodTableForInterface %s -----------\n", pItfClass->GetDebugClassName()));
pComMT.SuppressRelease();
RETURN pComMT;
}
ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForBasic(MethodTable* pMT)
{
CONTRACT (ComMethodTable*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
const unsigned cbExtraSlots = ComMethodTable::GetNumExtraSlots(ifDispatch);
CorClassIfaceAttr ClassItfType = pMT->GetComClassInterfaceType();
LOG((LF_INTEROP, LL_INFO1000, "CreateComMethodTableForBasic %s\n", pMT->GetDebugClassName()));
unsigned cbVtable = cbExtraSlots * sizeof(SLOT);
unsigned cbToAlloc = sizeof(ComMethodTable) + cbVtable;
AllocMemHolder<ComMethodTable> pComMT(pMT->GetLoaderAllocator()->GetStubHeap()->AllocMem(S_SIZE_T(cbToAlloc)));
ExecutableWriterHolder<ComMethodTable> comMTWriterHolder(pComMT, cbToAlloc);
ComMethodTable* pComMTRW = comMTWriterHolder.GetRW();
// set up the header
pComMTRW->m_ptReserved = (SLOT)(size_t)0xDEADC0FF;
pComMTRW->m_pMT = pMT;
pComMTRW->m_cbSlots = 0; // number of slots not counting IUnk
pComMTRW->m_cbRefCount = 0;
pComMTRW->m_pMDescr = NULL;
pComMTRW->m_pITypeInfo = NULL;
pComMTRW->m_pDispatchInfo = NULL;
// Initialize the flags.
pComMTRW->m_Flags = enum_IsBasic;
pComMTRW->m_Flags |= enum_ClassVtableMask | ClassItfType;
// Set the IID of the interface.
pComMTRW->m_IID = IID_IUnknown;
pComMTRW->m_Flags |= enum_GuidGenerated;
// Determine if the interface is visible from COM.
if (IsTypeVisibleFromCom(TypeHandle(pComMT->m_pMT)))
pComMTRW->m_Flags |= enum_ComVisible;
// Determine if the interface is a COM imported class interface.
if (pMT->GetClass()->IsComClassInterface())
pComMTRW->m_Flags |= enum_ComClassItf;
#ifdef _DEBUG_0xDEADCA11
{
// In debug set all the vtable slots to 0xDEADCA11.
SLOT *pComVTable = (SLOT*)(pComMTRW + 1);
for (unsigned iComSlots = 0; iComSlots < DEBUG_AssertSlots + cbExtraSlots; iComSlots++)
*(pComVTable + iComSlots) = (SLOT)(size_t)0xDEADCA11;
}
#endif
LOG((LF_INTEROP, LL_INFO1000, "---------- end of CreateComMethodTableForBasic %s -----------\n", pMT->GetDebugClassName()));
pComMT.SuppressRelease();
RETURN pComMT;
}
//--------------------------------------------------------------------------
// Creates a ComMethodTable for an interface and stores it in the m_rgpIPtr array.
//--------------------------------------------------------------------------
ComMethodTable *ComCallWrapperTemplate::InitializeForInterface(MethodTable *pParentMT, MethodTable *pItfMT, DWORD dwIndex)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
ComMethodTable *pItfComMT = NULL;
if (m_pParent != NULL)
{
pItfComMT = m_pParent->GetComMTForItf(pItfMT);
if (pItfComMT != NULL)
{
// if the parent COM MT is not a trivial aggregate, simple MethodTable slot check is enough
if (!m_thClass.GetMethodTable()->ImplementsInterfaceWithSameSlotsAsParent(pItfMT, pParentMT))
{
// the interface is implemented by parent but this class reimplemented
// its method(s) so we will need to build a new COM vtable for it
pItfComMT = NULL;
}
}
}
if (pItfComMT == NULL)
{
// we couldn't use parent's vtable so we create a new one
pItfComMT = CreateComMethodTableForInterface(pItfMT);
}
m_rgpIPtr[dwIndex] = (SLOT*)(pItfComMT + 1);
pItfComMT->AddRef();
// update pItfMT in case code:CreateComMethodTableForInterface decided to redirect the interface
pItfMT = pItfComMT->GetMethodTable();
if (pItfMT == CoreLibBinder::GetExistingClass(CLASS__ICUSTOM_QUERYINTERFACE))
{
m_flags |= enum_ImplementsICustomQueryInterface;
}
else if (InlineIsEqualGUID(pItfComMT->GetIID(), IID_IMarshal))
{
// detect IMarshal so we can handle IAgileObject in a backward compatible way
m_flags |= enum_ImplementsIMarshal;
}
return pItfComMT;
}
//--------------------------------------------------------------------------
// ComCallWrapper* ComCallWrapper::CreateTemplate(TypeHandle thClass)
// create a template wrapper, which is cached in the class
// used for initializing other wrappers for instances of the class
//--------------------------------------------------------------------------
ComCallWrapperTemplate* ComCallWrapperTemplate::CreateTemplate(TypeHandle thClass)
{
CONTRACT (ComCallWrapperTemplate*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(!thClass.IsNull());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
GCX_PREEMP();
if (!thClass.IsTypeDesc())
{
// Canonicalize the class type because we are going to stick the template pointer to EEClass.
thClass = thClass.GetCanonicalMethodTable();
}
MethodTable *pMT = thClass.GetMethodTable();
MethodTable *pParentMT = pMT->GetComPlusParentMethodTable();
ComCallWrapperTemplate *pParentTemplate = NULL;
unsigned iItf = 0;
// Create the parent's template if it has not been created yet.
if (pParentMT)
{
pParentTemplate = pParentMT->GetComCallWrapperTemplate();
if (!pParentTemplate)
pParentTemplate = CreateTemplate(pParentMT);
}
// Preload the policy for this interface
CCWInterfaceMapIterator it(thClass);
// Num interfaces in the template.
unsigned numInterfaces = it.GetCount();
// Check to see if another thread has already set up the template.
{
// Move this inside the scope so it is destroyed before its memory is.
ComCallWrapperTemplateHolder pTemplate = NULL;
pTemplate = thClass.GetComCallWrapperTemplate();
if (pTemplate)
{
pTemplate.SuppressRelease();
RETURN pTemplate;
}
// Allocate the template.
pTemplate = (ComCallWrapperTemplate*)new BYTE[sizeof(ComCallWrapperTemplate) + numInterfaces * sizeof(SLOT)];
// Store the information required by the template.
// Also, eagerly set vars to NULL, in case we are interrupted during construction
// and try to destruct the template.
memset(pTemplate->m_rgpIPtr, 0, numInterfaces * sizeof(SLOT));
pTemplate->m_thClass = thClass;
pTemplate->m_cbInterfaces = numInterfaces;
pTemplate->m_pParent = pParentTemplate;
pTemplate->m_cbRefCount = 1;
pTemplate->m_pClassComMT = NULL; // Defer setting this up.
pTemplate->m_pBasicComMT = NULL;
pTemplate->m_pDefaultItf = NULL;
pTemplate->m_pICustomQueryInterfaceGetInterfaceMD = NULL;
pTemplate->m_flags = 0;
// Determine the COM visibility of classes in our hierarchy.
pTemplate->DetermineComVisibility();
// Eagerly create the basic CMT.
pTemplate->m_pBasicComMT = pTemplate->CreateComMethodTableForBasic(pMT);
pTemplate->m_pBasicComMT->AddRef();
if (ClassSupportsIClassX(pMT))
{
// we will allow building IClassX for the class
pTemplate->m_flags |= enum_SupportsIClassX;
}
if (IsOleAutDispImplRequiredForClass(pMT))
{
// Determine what IDispatch implementation this class should use
pTemplate->m_flags |= enum_UseOleAutDispatchImpl;
}
// Eagerly create the interface CMTs.
// when iterate the interfaces implemented by the methodtable, we can check whether
// the interface supports ICustomQueryInterface.
CoreLibBinder::GetClass(CLASS__ICUSTOM_QUERYINTERFACE);
it.Reset();
while (it.Next())
{
MethodTable *pItfMT = it.GetInterface();
ComMethodTable *pItfComMT = pTemplate->InitializeForInterface(pParentMT, pItfMT, it.GetIndex());
}
// Cache the template in class.
if (!thClass.SetComCallWrapperTemplate(pTemplate))
{
// another thread beat us to it
pTemplate = thClass.GetComCallWrapperTemplate();
_ASSERTE(pTemplate != NULL);
pTemplate.SuppressRelease();
RETURN pTemplate;
}
pTemplate.SuppressRelease();
#ifdef PROFILING_SUPPORTED
// Notify profiler of the CCW, so it can avoid double-counting.
if (pTemplate->SupportsIClassX())
{
BEGIN_PROFILER_CALLBACK(CORProfilerTrackCCW());
// When under the profiler, we'll eagerly generate the IClassX CMT.
pTemplate->GetClassComMT();
IID IClassXIID = GUID_NULL;
SLOT *pComVtable = (SLOT *)(pTemplate->m_pClassComMT + 1);
// If the class is visible from COM, then give out the IClassX IID.
if (pTemplate->m_pClassComMT->IsComVisible())
GenerateClassItfGuid(thClass, &IClassXIID);
#if defined(_DEBUG)
WCHAR rIID[40]; // {00000000-0000-0000-0000-000000000000}
GuidToLPWSTR(IClassXIID, rIID, ARRAY_SIZE(rIID));
SString ssName;
thClass.GetName(ssName);
LOG((LF_CORPROF, LL_INFO100, "COMClassicVTableCreated Class:%ls, IID:%ls, vTbl:%#08x\n",
ssName.GetUnicode(), rIID, pComVtable));
#else
LOG((LF_CORPROF, LL_INFO100, "COMClassicVTableCreated TypeHandle:%#x, IID:{%08x-...}, vTbl:%#08x\n",
thClass.AsPtr(), IClassXIID.Data1, pComVtable));
#endif
(&g_profControlBlock)->COMClassicVTableCreated(
(ClassID) thClass.AsPtr(), IClassXIID, pComVtable,
pTemplate->m_pClassComMT->m_cbSlots +
ComMethodTable::GetNumExtraSlots(pTemplate->m_pClassComMT->GetInterfaceType()));
END_PROFILER_CALLBACK();
}
#endif // PROFILING_SUPPORTED
RETURN pTemplate;
}
}
//--------------------------------------------------------------------------
// Creates a new Template for just one interface.
//--------------------------------------------------------------------------
ComCallWrapperTemplate *ComCallWrapperTemplate::CreateTemplateForInterface(MethodTable *pItfMT)
{
CONTRACT (ComCallWrapperTemplate*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(COMPlusThrowOM());
PRECONDITION(CheckPointer(pItfMT));
PRECONDITION(pItfMT->IsInterface());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
GCX_PREEMP();
// Num interfaces in the template.
unsigned numInterfaces = 1;
// Allocate the template.
ComCallWrapperTemplateHolder pTemplate = pItfMT->GetComCallWrapperTemplate();
if (pTemplate)
{
pTemplate.SuppressRelease();
RETURN pTemplate;
}
pTemplate = (ComCallWrapperTemplate *)new BYTE[sizeof(ComCallWrapperTemplate) + numInterfaces * sizeof(SLOT)];
// Store the information required by the template.
// Also, eagerly set vars to NULL, in case we are interrupted during construction
// and try to destruct the template.
memset(pTemplate->m_rgpIPtr, 0, numInterfaces * sizeof(SLOT));
pTemplate->m_thClass = TypeHandle(pItfMT);
pTemplate->m_cbInterfaces = numInterfaces;
pTemplate->m_pParent = NULL;
pTemplate->m_cbRefCount = 1;
pTemplate->m_pClassComMT = NULL;
pTemplate->m_pBasicComMT = NULL;
pTemplate->m_pDefaultItf = pItfMT;
pTemplate->m_pICustomQueryInterfaceGetInterfaceMD = NULL;
pTemplate->m_flags = enum_RepresentsVariantInterface;
// Initialize the one ComMethodTable
ComMethodTable *pItfComMT;
pItfComMT = pTemplate->InitializeForInterface(NULL, pItfMT, 0);
// Cache the template on the interface.
if (!pItfMT->SetComCallWrapperTemplate(pTemplate))
{
// another thread beat us to it
pTemplate = pItfMT->GetComCallWrapperTemplate();
_ASSERTE(pTemplate != NULL);
}
pTemplate.SuppressRelease();
RETURN pTemplate;
}
void ComCallWrapperTemplate::DetermineComVisibility()
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
m_flags &= (~enum_InvisibleParent);
if (m_pParent == NULL)
return;
// Check if the parent has an invisible parent
// or if the parent itself is invisible.
if (m_pParent->HasInvisibleParent()
|| !IsTypeVisibleFromCom(m_pParent->m_thClass))
{
_ASSERTE(NULL != FindInvisibleParent());
m_flags |= enum_InvisibleParent;
}
}
ComCallWrapperTemplate* ComCallWrapperTemplate::FindInvisibleParent()
{
ComCallWrapperTemplate* invisParentMaybe = m_pParent;
// Walk up the CCW parent classes and try to find
// if one is invisible to COM.
while (invisParentMaybe != NULL)
{
// If our parent is invisible, return it.
if (!IsTypeVisibleFromCom(invisParentMaybe->m_thClass))
return invisParentMaybe;
invisParentMaybe = invisParentMaybe->m_pParent;
}
// All classes in hierarchy are COM visible
return NULL;
}
//--------------------------------------------------------------------------
// ComCallWrapperTemplate* ComCallWrapperTemplate::GetTemplate(TypeHandle thClass)
// look for a template in the method table, if not create one
//--------------------------------------------------------------------------
ComCallWrapperTemplate* ComCallWrapperTemplate::GetTemplate(TypeHandle thType)
{
CONTRACT (ComCallWrapperTemplate*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
// Check to see if the specified class already has a template set up.
ComCallWrapperTemplate* pTemplate = thType.GetComCallWrapperTemplate();
if (pTemplate)
RETURN pTemplate;
// Create the template and return it. CreateTemplate will take care of synchronization.
if (thType.IsInterface())
{
RETURN CreateTemplateForInterface(thType.AsMethodTable());
}
else
{
RETURN CreateTemplate(thType);
}
}
//--------------------------------------------------------------------------
// ComMethodTable *ComCallWrapperTemplate::SetupComMethodTableForClass(MethodTable *pMT)
// Sets up the wrapper template for the speficied class and sets up a COM
// method table for the IClassX interface of the specified class. If the
// bLayOutComMT flag is set then if the IClassX COM method table has not
// been laid out yet then it will be.
//--------------------------------------------------------------------------
ComMethodTable *ComCallWrapperTemplate::SetupComMethodTableForClass(MethodTable *pMT, BOOL bLayOutComMT)
{
CONTRACT (ComMethodTable*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(!pMT->IsInterface());
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
// Retrieve the COM call wrapper template for the class.
ComCallWrapperTemplate *pTemplate = GetTemplate(pMT);
// Retrieve the IClassX COM method table.
ComMethodTable *pIClassXComMT = pTemplate->GetClassComMT();
_ASSERTE(pIClassXComMT);
// Lay out the IClassX COM method table if it hasn't been laid out yet and
// the bLayOutComMT flag is set.
if (!pIClassXComMT->IsLayoutComplete() && bLayOutComMT)
{
pIClassXComMT->LayOutClassMethodTable();
_ASSERTE(pIClassXComMT->IsLayoutComplete());
}
RETURN pIClassXComMT;
}
MethodDesc * ComCallWrapperTemplate::GetICustomQueryInterfaceGetInterfaceMD()
{
CONTRACT (MethodDesc*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(m_flags & enum_ImplementsICustomQueryInterface);
}
CONTRACT_END;
if (m_pICustomQueryInterfaceGetInterfaceMD == NULL)
m_pICustomQueryInterfaceGetInterfaceMD = m_thClass.GetMethodTable()->GetMethodDescForInterfaceMethod(
CoreLibBinder::GetMethod(METHOD__ICUSTOM_QUERYINTERFACE__GET_INTERFACE),
TRUE /* throwOnConflict */);
RETURN m_pICustomQueryInterfaceGetInterfaceMD;
}
//--------------------------------------------------------------------------
// Module* ComCallMethodDesc::GetModule()
// Get Module
//--------------------------------------------------------------------------
Module* ComCallMethodDesc::GetModule()
{
CONTRACT (Module*)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION( IsFieldCall() ? (m_pFD != NULL) : (m_pMD != NULL) );
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
MethodTable* pClass = (IsFieldCall()) ? m_pFD->GetEnclosingMethodTable() : m_pMD->GetMethodTable();
_ASSERTE(pClass != NULL);
RETURN pClass->GetModule();
}