src/coreclr/vm/dllimport.cpp (4,475 lines of code) (raw):
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
// File: DllImport.cpp
//
//
// P/Invoke support.
//
#include "common.h"
#include "vars.hpp"
#include "stublink.h"
#include "threads.h"
#include "excep.h"
#include "dllimport.h"
#include "method.hpp"
#include "siginfo.hpp"
#include "callconvbuilder.hpp"
#include "comdelegate.h"
#include "ceeload.h"
#include "mlinfo.h"
#include "eeconfig.h"
#include "comutilnative.h"
#include "corhost.h"
#include "asmconstants.h"
#include "customattribute.h"
#include "ilstubcache.h"
#include "typeparse.h"
#include "typestring.h"
#include "sigbuilder.h"
#include "sigformat.h"
#include "ecall.h"
#include "qcall.h"
#include "fieldmarshaler.h"
#include "pinvokeoverride.h"
#include "nativelibrary.h"
#include "interoplibinterface.h"
#include <formattype.h>
#include "../md/compiler/custattr.h"
#ifdef FEATURE_COMINTEROP
#include "runtimecallablewrapper.h"
#include "clrtocomcall.h"
#endif // FEATURE_COMINTEROP
#include "eventtrace.h"
namespace
{
void AppendEHClause(int nClauses, COR_ILMETHOD_SECT_EH * pEHSect, ILStubEHClause * pClause, int * pCurIdx)
{
LIMITED_METHOD_CONTRACT;
if (pClause->kind == ILStubEHClause::kNone)
return;
int idx = *pCurIdx;
*pCurIdx = idx + 1;
CorExceptionFlag flags;
switch (pClause->kind)
{
case ILStubEHClause::kFinally: flags = COR_ILEXCEPTION_CLAUSE_FINALLY; break;
case ILStubEHClause::kTypedCatch: flags = COR_ILEXCEPTION_CLAUSE_NONE; break;
default:
UNREACHABLE_MSG("unexpected ILStubEHClause kind");
}
_ASSERTE(idx < nClauses);
pEHSect->Fat.Clauses[idx].Flags = flags;
pEHSect->Fat.Clauses[idx].TryOffset = pClause->dwTryBeginOffset;
pEHSect->Fat.Clauses[idx].TryLength = pClause->cbTryLength;
pEHSect->Fat.Clauses[idx].HandlerOffset = pClause->dwHandlerBeginOffset;
pEHSect->Fat.Clauses[idx].HandlerLength = pClause->cbHandlerLength;
pEHSect->Fat.Clauses[idx].ClassToken = pClause->dwTypeToken;
}
VOID PopulateEHSect(COR_ILMETHOD_SECT_EH * pEHSect, int nClauses, ILStubEHClause * pOne, ILStubEHClause * pTwo)
{
LIMITED_METHOD_CONTRACT;
pEHSect->Fat.Kind = (CorILMethod_Sect_EHTable | CorILMethod_Sect_FatFormat);
pEHSect->Fat.DataSize = COR_ILMETHOD_SECT_EH_FAT::Size(nClauses);
int curIdx = 0;
AppendEHClause(nClauses, pEHSect, pOne, &curIdx);
AppendEHClause(nClauses, pEHSect, pTwo, &curIdx);
}
}
StubSigDesc::StubSigDesc(MethodDesc *pMD)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
PRECONDITION(pMD != NULL);
}
CONTRACTL_END;
m_pMD = pMD;
m_pMT = nullptr;
m_sig = pMD->GetSignature();
m_pModule = pMD->GetModule(); // Used for token resolution.
m_tkMethodDef = pMD->GetMemberDef();
SigTypeContext::InitTypeContext(pMD, &m_typeContext);
m_pMetadataModule = pMD->GetModule();
m_pLoaderModule = pMD->GetLoaderModule(); // Used for ILStubCache selection and MethodTable creation.
INDEBUG(InitDebugNames());
}
StubSigDesc::StubSigDesc(MethodDesc* pMD, const Signature& sig, Module* pModule, Module* pLoaderModule)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
PRECONDITION(!sig.IsEmpty());
PRECONDITION(pModule != NULL);
}
CONTRACTL_END;
m_pMD = pMD;
m_pMT = nullptr;
m_sig = sig;
m_pModule = pModule;
if (pMD != NULL)
{
m_tkMethodDef = pMD->GetMemberDef();
SigTypeContext::InitTypeContext(pMD, &m_typeContext);
m_pMetadataModule = pMD->GetModule();
m_pLoaderModule = pLoaderModule == NULL ? pMD->GetLoaderModule() : pLoaderModule; // Used for ILStubCache selection and MethodTable creation.
}
else
{
m_tkMethodDef = mdMethodDefNil;
m_pMetadataModule = m_pModule;
m_pLoaderModule = pLoaderModule == NULL ? m_pModule : pLoaderModule;
}
INDEBUG(InitDebugNames());
}
StubSigDesc::StubSigDesc(MethodTable* pMT, const Signature& sig, Module* pModule)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
PRECONDITION(!sig.IsEmpty());
PRECONDITION(pModule != NULL);
}
CONTRACTL_END;
m_pMD = nullptr;
m_pMT = pMT;
m_sig = sig;
m_pModule = pModule;
m_tkMethodDef = mdMethodDefNil;
if (pMT != NULL)
{
SigTypeContext::InitTypeContext(pMT, &m_typeContext);
m_pMetadataModule = pMT->GetModule();
m_pLoaderModule = pMT->GetLoaderModule();
}
else
{
m_pLoaderModule = m_pModule;
}
INDEBUG(InitDebugNames());
}
StubSigDesc::StubSigDesc(const Signature& sig, Module* pModule)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
PRECONDITION(!sig.IsEmpty());
PRECONDITION(pModule != NULL);
}
CONTRACTL_END;
m_pMD = nullptr;
m_pMT = nullptr;
m_sig = sig;
m_pModule = pModule;
m_tkMethodDef = mdMethodDefNil;
m_pMetadataModule = m_pModule;
m_pLoaderModule = m_pModule;
INDEBUG(InitDebugNames());
}
#ifndef DACCESS_COMPILE
class StubState
{
public:
virtual void SetLastError(BOOL fSetLastError) = 0;
virtual void BeginEmit(DWORD dwStubFlags) = 0;
virtual void MarshalReturn(MarshalInfo* pInfo, int argOffset) = 0;
virtual void MarshalArgument(MarshalInfo* pInfo, int argOffset, UINT nativeStackOffset) = 0;
virtual void MarshalLCID(int argIdx) = 0;
virtual void MarshalField(MarshalInfo* pInfo, UINT32 managedOffset, UINT32 nativeOffset, FieldDesc* pFieldDesc) = 0;
virtual void EmitInvokeTarget(MethodDesc *pStubMD) = 0;
virtual void FinishEmit(MethodDesc* pMD) = 0;
virtual ~StubState()
{
LIMITED_METHOD_CONTRACT;
}
};
class ILStubState : public StubState
{
protected:
ILStubState(
Module* pStubModule,
const Signature &signature,
SigTypeContext* pTypeContext,
DWORD dwStubFlags,
int iLCIDParamIdx,
MethodDesc* pTargetMD)
: m_slIL(dwStubFlags, pStubModule, signature, pTypeContext, pTargetMD, iLCIDParamIdx)
, m_dwStubFlags(dwStubFlags)
{
STANDARD_VM_CONTRACT;
m_fSetLastError = 0;
}
public:
void SetLastError(BOOL fSetLastError)
{
LIMITED_METHOD_CONTRACT;
m_fSetLastError = fSetLastError;
}
// We use three stub linkers to generate IL stubs. The pre linker is the main one. It does all the marshaling and
// then calls the target method. The post return linker is only used to unmarshal the return value after we return
// from the target method. The post linker handles all the unmarshaling for by ref arguments and clean-up. It
// also checks if we should throw an exception etc.
//
// Currently, we have two "emittable" ILCodeLabel's. The first one is at the beginning of the pre linker. This
// label is used to emit code to declare and initialize clean-up flags. Each argument which requires clean-up
// emits one flag. This flag is set only after the marshaling is done, and it is checked before we do any clean-up
// in the finally.
//
// The second "emittable" ILCodeLabel is at the beginning of the post linker. It is used to emit code which is
// not safe to run in the case of an exception. The rest of the post linker is wrapped in a finally, and it contains
// with the necessary clean-up which should be executed in both normal and exception cases.
void BeginEmit(DWORD dwStubFlags)
{
WRAPPER_NO_CONTRACT;
m_slIL.Begin(dwStubFlags);
_ASSERTE(m_dwStubFlags == dwStubFlags);
}
void MarshalReturn(MarshalInfo* pInfo, int argOffset)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pInfo));
}
CONTRACTL_END;
pInfo->GenerateReturnIL(&m_slIL, argOffset,
SF_IsForwardStub(m_dwStubFlags),
SF_IsFieldGetterStub(m_dwStubFlags),
SF_IsHRESULTSwapping(m_dwStubFlags));
}
void MarshalArgument(MarshalInfo* pInfo, int argOffset, UINT nativeStackOffset)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pInfo));
}
CONTRACTL_END;
pInfo->GenerateArgumentIL(&m_slIL, argOffset, nativeStackOffset, SF_IsForwardStub(m_dwStubFlags));
}
void MarshalField(MarshalInfo* pInfo, UINT32 managedOffset, UINT32 nativeOffset, FieldDesc* pFieldDesc)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pInfo));
}
CONTRACTL_END;
pInfo->GenerateFieldIL(&m_slIL, managedOffset, nativeOffset, pFieldDesc);
}
#ifdef FEATURE_COMINTEROP
static void EmitInterfaceClearNative(ILCodeStream* pcsEmit, DWORD dwLocalNum)
{
STANDARD_VM_CONTRACT;
ILCodeLabel *pSkipClearNativeLabel = pcsEmit->NewCodeLabel();
pcsEmit->EmitLDLOC(dwLocalNum);
pcsEmit->EmitBRFALSE(pSkipClearNativeLabel);
pcsEmit->EmitLDLOC(dwLocalNum);
pcsEmit->EmitCALL(METHOD__INTERFACEMARSHALER__CLEAR_NATIVE, 1, 0);
pcsEmit->EmitLabel(pSkipClearNativeLabel);
}
#endif // FEATURE_COMINTEROP
void MarshalLCID(int argIdx)
{
STANDARD_VM_CONTRACT;
ILCodeStream* pcs = m_slIL.GetDispatchCodeStream();
if (SF_IsReverseStub(m_dwStubFlags))
{
if ((m_slIL.GetStubTargetCallingConv() & IMAGE_CEE_CS_CALLCONV_HASTHIS) == IMAGE_CEE_CS_CALLCONV_HASTHIS)
{
// the arg number will be incremented by LDARG if we are in an instance method
_ASSERTE(argIdx > 0);
argIdx--;
}
// call CultureInfo.get_CurrentCulture()
pcs->EmitCALL(METHOD__CULTURE_INFO__GET_CURRENT_CULTURE, 0, 1);
// save the current culture
LocalDesc locDescCulture(CoreLibBinder::GetClass(CLASS__CULTURE_INFO));
DWORD dwCultureLocalNum = pcs->NewLocal(locDescCulture);
pcs->EmitSTLOC(dwCultureLocalNum);
// set a new one based on the LCID passed from unmanaged
pcs->EmitLDARG(argIdx);
// call CultureInfo..ctor(lcid)
// call CultureInfo.set_CurrentCulture(culture)
pcs->EmitNEWOBJ(METHOD__CULTURE_INFO__INT_CTOR, 1);
pcs->EmitCALL(METHOD__CULTURE_INFO__SET_CURRENT_CULTURE, 1, 0);
// and restore the current one after the call
m_slIL.SetCleanupNeeded();
ILCodeStream *pcsCleanup = m_slIL.GetCleanupCodeStream();
// call CultureInfo.set_CurrentCulture(original_culture)
pcsCleanup->EmitLDLOC(dwCultureLocalNum);
pcsCleanup->EmitCALL(METHOD__CULTURE_INFO__SET_CURRENT_CULTURE, 1, 0);
}
else
{
if (SF_IsCOMStub(m_dwStubFlags))
{
// We used to get LCID from current thread's culture here. The code
// was replaced by the hardcoded LCID_ENGLISH_US as requested by VSTO.
pcs->EmitLDC(0x0409); // LCID_ENGLISH_US
}
else
{
// call CultureInfo.get_CurrentCulture()
pcs->EmitCALL(METHOD__CULTURE_INFO__GET_CURRENT_CULTURE, 0, 1);
//call CultureInfo.get_LCID(this)
pcs->EmitCALL(METHOD__CULTURE_INFO__GET_ID, 1, 1);
}
}
// add the extra arg to the unmanaged signature
LocalDesc locDescNative(ELEMENT_TYPE_I4);
pcs->SetStubTargetArgType(&locDescNative, false);
}
void SwapStubSignatures(MethodDesc* pStubMD)
{
STANDARD_VM_CONTRACT;
//
// Since the stub handles native-to-managed transitions, we have to swap the
// stub-state-calculated stub target sig with the stub sig itself. This is
// because the stub target sig represents the native signature and the stub
// sig represents the managed signature.
//
// The first step is to convert the managed signature to a module-independent
// signature and then pass it off to SetStubTargetMethodSig. Note that the
// ILStubResolver will copy the sig, so we only need to make a temporary copy
// of it.
//
SigBuilder sigBuilder;
{
SigPointer sigPtr(pStubMD->GetSig());
sigPtr.ConvertToInternalSignature(pStubMD->GetModule(), NULL, &sigBuilder);
}
//
// The second step is to reset the sig on the stub MethodDesc to be the
// stub-state-calculated stub target sig.
//
{
//
// make a domain-local copy of the sig so that this state can outlive the
// compile time state.
//
DWORD cbNewSig;
PCCOR_SIGNATURE pNewSig;
cbNewSig = GetStubTargetMethodSigLength();
pNewSig = (PCCOR_SIGNATURE)(void *)pStubMD->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(cbNewSig));
memcpyNoGCRefs((void *)pNewSig, GetStubTargetMethodSig(), cbNewSig);
pStubMD->AsDynamicMethodDesc()->SetStoredMethodSig(pNewSig, cbNewSig);
SigPointer sigPtr(pNewSig, cbNewSig);
uint32_t callConvInfo;
IfFailThrow(sigPtr.GetCallingConvInfo(&callConvInfo));
if (callConvInfo & CORINFO_CALLCONV_HASTHIS)
{
((PTR_DynamicMethodDesc)pStubMD)->ClearFlags(DynamicMethodDesc::FlagStatic);
pStubMD->ClearStatic();
}
else
{
((PTR_DynamicMethodDesc)pStubMD)->SetFlags(DynamicMethodDesc::FlagStatic);
pStubMD->SetStatic();
}
#ifndef TARGET_X86
// we store the real managed argument stack size in the stub MethodDesc on non-X86
UINT stackSize = pStubMD->SizeOfNativeArgStack();
if (!FitsInU2(stackSize))
COMPlusThrow(kMarshalDirectiveException, IDS_EE_SIGTOOCOMPLEX);
pStubMD->AsDynamicMethodDesc()->SetNativeStackArgSize(static_cast<WORD>(stackSize));
#endif // TARGET_X86
}
DWORD cbTempModuleIndependentSigLength;
BYTE * pTempModuleIndependentSig = (BYTE *)sigBuilder.GetSignature(&cbTempModuleIndependentSigLength);
// Finish it
SetStubTargetMethodSig(pTempModuleIndependentSig,
cbTempModuleIndependentSigLength);
}
void EmitInvokeTarget(MethodDesc *pStubMD)
{
STANDARD_VM_CONTRACT;
m_slIL.DoNDirect(m_slIL.GetDispatchCodeStream(), m_dwStubFlags, pStubMD);
}
virtual void EmitExceptionHandler(LocalDesc* pNativeReturnType, LocalDesc* pManagedReturnType,
ILCodeLabel** ppTryBeginLabel, ILCodeLabel** ppTryEndAndCatchBeginLabel, ILCodeLabel** ppCatchEndAndReturnLabel)
{
#ifdef FEATURE_COMINTEROP
STANDARD_VM_CONTRACT;
ILCodeStream* pcsExceptionHandler = m_slIL.NewCodeStream(ILStubLinker::kExceptionHandler);
*ppTryEndAndCatchBeginLabel = pcsExceptionHandler->NewCodeLabel(); // try ends at the same place the catch begins
*ppCatchEndAndReturnLabel = pcsExceptionHandler->NewCodeLabel(); // catch ends at the same place we resume afterwards
pcsExceptionHandler->EmitLEAVE(*ppCatchEndAndReturnLabel);
pcsExceptionHandler->EmitLabel(*ppTryEndAndCatchBeginLabel);
BYTE nativeReturnElemType = pNativeReturnType->ElementType[0]; // return type of the stub
BYTE managedReturnElemType = pManagedReturnType->ElementType[0]; // return type of the mananged target
bool returnTheHRESULT = SF_IsHRESULTSwapping(m_dwStubFlags) ||
(managedReturnElemType == ELEMENT_TYPE_I4) ||
(managedReturnElemType == ELEMENT_TYPE_U4);
DWORD retvalLocalNum = m_slIL.GetReturnValueLocalNum();
BinderMethodID getHRForException;
getHRForException = METHOD__MARSHAL__GET_HR_FOR_EXCEPTION;
pcsExceptionHandler->EmitCALL(getHRForException,
0, // WARNING: This method takes 1 input arg, the exception object. But the ILStubLinker
// has no knowledge that the exception object is on the stack (because it is
// unaware that we've just entered a catch block), so we lie and claim that we
// don't take any input arguments.
1);
switch (nativeReturnElemType)
{
default:
UNREACHABLE_MSG("Unexpected element type found on native return type.");
break;
case ELEMENT_TYPE_VOID:
_ASSERTE(retvalLocalNum == (DWORD)-1);
pcsExceptionHandler->EmitPOP();
break;
case ELEMENT_TYPE_I4:
case ELEMENT_TYPE_U4:
{
if (!returnTheHRESULT)
{
pcsExceptionHandler->EmitPOP();
pcsExceptionHandler->EmitLDC(0);
pcsExceptionHandler->EmitCONV_T((CorElementType)nativeReturnElemType);
}
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitSTLOC(retvalLocalNum);
}
break;
case ELEMENT_TYPE_R4:
pcsExceptionHandler->EmitPOP();
pcsExceptionHandler->EmitLDC_R4(CLR_NAN_32);
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitSTLOC(retvalLocalNum);
break;
case ELEMENT_TYPE_R8:
pcsExceptionHandler->EmitPOP();
pcsExceptionHandler->EmitLDC_R8(CLR_NAN_64);
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitSTLOC(retvalLocalNum);
break;
case ELEMENT_TYPE_INTERNAL:
{
TypeHandle returnTypeHnd = pNativeReturnType->InternalToken;
CONSISTENCY_CHECK(returnTypeHnd.IsValueType());
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitLDLOCA(retvalLocalNum);
pcsExceptionHandler->EmitINITOBJ(m_slIL.GetDispatchCodeStream()->GetToken(returnTypeHnd));
}
break;
case ELEMENT_TYPE_PTR:
pcsExceptionHandler->EmitPOP();
pcsExceptionHandler->EmitLDC(0);
pcsExceptionHandler->EmitCONV_U();
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitSTLOC(retvalLocalNum);
break;
case ELEMENT_TYPE_BOOLEAN:
case ELEMENT_TYPE_CHAR:
case ELEMENT_TYPE_I1:
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_I2:
case ELEMENT_TYPE_U2:
case ELEMENT_TYPE_I8:
case ELEMENT_TYPE_U8:
case ELEMENT_TYPE_I:
case ELEMENT_TYPE_U:
pcsExceptionHandler->EmitPOP();
pcsExceptionHandler->EmitLDC(0);
pcsExceptionHandler->EmitCONV_T((CorElementType)nativeReturnElemType);
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitSTLOC(retvalLocalNum);
break;
}
pcsExceptionHandler->EmitLEAVE(*ppCatchEndAndReturnLabel);
pcsExceptionHandler->EmitLabel(*ppCatchEndAndReturnLabel);
if (nativeReturnElemType != ELEMENT_TYPE_VOID)
{
_ASSERTE(retvalLocalNum != (DWORD)-1);
pcsExceptionHandler->EmitLDLOC(retvalLocalNum);
}
pcsExceptionHandler->EmitRET();
#endif // FEATURE_COMINTEROP
}
#ifndef DACCESS_COMPILE
void FinishEmit(MethodDesc* pStubMD)
{
STANDARD_VM_CONTRACT;
ILCodeStream* pcsMarshal = m_slIL.GetMarshalCodeStream();
ILCodeStream* pcsUnmarshal = m_slIL.GetUnmarshalCodeStream();
ILCodeStream* pcsDispatch = m_slIL.GetDispatchCodeStream();
if (SF_IsHRESULTSwapping(m_dwStubFlags) && m_slIL.StubHasVoidReturnType())
{
// if the return type is void, but we're doing HRESULT swapping, we
// need to set the return type here. Otherwise, the return value
// marshaler will do this.
pcsMarshal->SetStubTargetReturnType(ELEMENT_TYPE_I4); // HRESULT
if (SF_IsReverseStub(m_dwStubFlags))
{
// reverse interop needs to seed the return value if the
// managed function returns void but we're doing hresult
// swapping.
pcsUnmarshal->EmitLDC(S_OK);
}
}
LocalDesc nativeReturnType;
LocalDesc managedReturnType;
bool hasTryCatchForHRESULT = SF_IsReverseCOMStub(m_dwStubFlags)
&& !SF_IsFieldGetterStub(m_dwStubFlags)
&& !SF_IsFieldSetterStub(m_dwStubFlags);
bool hasTryCatchExceptionHandler = hasTryCatchForHRESULT || SF_IsStructMarshalStub(m_dwStubFlags);
#ifdef FEATURE_COMINTEROP
if (hasTryCatchForHRESULT)
{
m_slIL.GetStubTargetReturnType(&nativeReturnType);
m_slIL.GetStubReturnType(&managedReturnType);
}
#endif // FEATURE_COMINTEROP
// Don't touch target signatures from this point on otherwise it messes up the
// cache in ILStubState::GetStubTargetMethodSig.
#ifdef _DEBUG
{
// The native and local signatures should not have any tokens.
// All token references should have been converted to
// ELEMENT_TYPE_INTERNAL.
//
// Note that MetaSig::GetReturnType and NextArg will normalize
// ELEMENT_TYPE_INTERNAL back to CLASS or VALUETYPE.
//
// <TODO> need to recursively check ELEMENT_TYPE_FNPTR signatures </TODO>
SigTypeContext typeContext; // this is an empty type context: COM calls are guaranteed to not be generics.
MetaSig nsig(
GetStubTargetMethodSig(),
GetStubTargetMethodSigLength(),
CoreLibBinder::GetModule(),
&typeContext);
CorElementType type;
IfFailThrow(nsig.GetReturnProps().PeekElemType(&type));
CONSISTENCY_CHECK(ELEMENT_TYPE_CLASS != type && ELEMENT_TYPE_VALUETYPE != type);
while (ELEMENT_TYPE_END != (type = nsig.NextArg()))
{
IfFailThrow(nsig.GetArgProps().PeekElemType(&type));
CONSISTENCY_CHECK(ELEMENT_TYPE_CLASS != type && ELEMENT_TYPE_VALUETYPE != type);
}
}
#endif // _DEBUG
// <NOTE>
// The profiler helpers below must be called immediately before and after the call to the target.
// The debugger trace call helpers are invoked from StubRareDisableWorker
// </NOTE>
#if defined(PROFILING_SUPPORTED)
DWORD dwMethodDescLocalNum = (DWORD)-1;
// Notify the profiler of call out of the runtime
if (!SF_IsReverseCOMStub(m_dwStubFlags) && !SF_IsReverseDelegateStub(m_dwStubFlags) && !SF_IsStructMarshalStub(m_dwStubFlags) && CORProfilerTrackTransitions())
{
dwMethodDescLocalNum = m_slIL.EmitProfilerBeginTransitionCallback(pcsDispatch, m_dwStubFlags);
_ASSERTE(dwMethodDescLocalNum != (DWORD)-1);
}
#endif // PROFILING_SUPPORTED
// For CoreClr, clear the last error before calling the target that returns last error.
// There isn't always a way to know the function have failed without checking last error,
// in particular on Unix.
if (m_fSetLastError && SF_IsForwardStub(m_dwStubFlags))
{
pcsDispatch->EmitCALL(METHOD__STUBHELPERS__CLEAR_LAST_ERROR, 0, 0);
}
// Invoke the target (calli, call method, call delegate, get/set field, etc.)
EmitInvokeTarget(pStubMD);
// Saving last error must be the first thing we do after returning from the target
if (m_fSetLastError && SF_IsForwardStub(m_dwStubFlags))
{
pcsDispatch->EmitCALL(METHOD__STUBHELPERS__SET_LAST_ERROR, 0, 0);
}
#if defined(TARGET_X86)
if (SF_IsForwardDelegateStub(m_dwStubFlags))
{
// the delegate may have an intercept stub attached to its sync block so we should
// prevent it from being garbage collected when the call is in progress
pcsDispatch->EmitLoadThis();
pcsDispatch->EmitCALL(METHOD__GC__KEEP_ALIVE, 1, 0);
}
#endif // defined(TARGET_X86)
#ifdef VERIFY_HEAP
if (SF_IsForwardStub(m_dwStubFlags) && g_pConfig->InteropValidatePinnedObjects())
{
// call StubHelpers.ValidateObject/StubHelpers.ValidateByref on pinned locals
m_slIL.EmitObjectValidation(pcsDispatch, m_dwStubFlags);
}
#endif // VERIFY_HEAP
#if defined(PROFILING_SUPPORTED)
// Notify the profiler of return back into the runtime
if (dwMethodDescLocalNum != (DWORD)-1)
{
m_slIL.EmitProfilerEndTransitionCallback(pcsDispatch, m_dwStubFlags, dwMethodDescLocalNum);
}
#endif // PROFILING_SUPPORTED
#ifdef FEATURE_COMINTEROP
if (SF_IsForwardCOMStub(m_dwStubFlags))
{
// Make sure that the RCW stays alive for the duration of the call. Note that if we do HRESULT
// swapping, we'll pass 'this' to GetCOMHRExceptionObject after returning from the target so
// GC.KeepAlive is not necessary.
if (!SF_IsHRESULTSwapping(m_dwStubFlags))
{
m_slIL.EmitLoadRCWThis(pcsDispatch, m_dwStubFlags);
pcsDispatch->EmitCALL(METHOD__GC__KEEP_ALIVE, 1, 0);
}
}
#endif // FEATURE_COMINTEROP
if (SF_IsHRESULTSwapping(m_dwStubFlags))
{
if (SF_IsForwardStub(m_dwStubFlags))
{
ILCodeLabel* pSkipThrowLabel = pcsDispatch->NewCodeLabel();
pcsDispatch->EmitDUP();
pcsDispatch->EmitLDC(0); // Compare against S_OK (i.e. 0).
pcsDispatch->EmitBGE(pSkipThrowLabel);
#ifdef FEATURE_COMINTEROP
if (SF_IsCOMStub(m_dwStubFlags))
{
m_slIL.EmitLoadStubContext(pcsDispatch, m_dwStubFlags);
m_slIL.EmitLoadRCWThis(pcsDispatch, m_dwStubFlags);
pcsDispatch->EmitCALL(METHOD__STUBHELPERS__GET_COM_HR_EXCEPTION_OBJECT, 3, 1);
}
else
#endif // FEATURE_COMINTEROP
{
pcsDispatch->EmitCALL(METHOD__STUBHELPERS__GET_HR_EXCEPTION_OBJECT, 1, 1);
}
pcsDispatch->EmitTHROW();
pcsDispatch->EmitLDC(0); // keep the IL stack balanced across the branch and the fall-through
pcsDispatch->EmitLabel(pSkipThrowLabel);
pcsDispatch->EmitPOP();
}
}
if (SF_IsCheckPendingException(m_dwStubFlags)
&& SF_IsForwardStub(m_dwStubFlags))
{
ILCodeLabel* pSkipThrowLabel = pcsDispatch->NewCodeLabel();
pcsDispatch->EmitCALL(METHOD__STUBHELPERS__GET_PENDING_EXCEPTION_OBJECT, 0, 1);
pcsDispatch->EmitDUP();
pcsDispatch->EmitBRFALSE(pSkipThrowLabel);
pcsDispatch->EmitTHROW();
pcsDispatch->EmitLDC(0); // keep the IL stack balanced across the branch and the fall-through
pcsDispatch->EmitLabel(pSkipThrowLabel);
pcsDispatch->EmitPOP();
}
m_slIL.End(m_dwStubFlags);
if (!hasTryCatchForHRESULT) // we will 'leave' the try scope and then 'ret' from outside
{
pcsUnmarshal->EmitRET();
}
CORJIT_FLAGS jitFlags(CORJIT_FLAGS::CORJIT_FLAG_IL_STUB);
if (m_slIL.HasInteropParamExceptionInfo())
{
// This code will not use the secret parameter, so we do not
// tell the JIT to bother with it.
m_slIL.ClearCode();
m_slIL.GenerateInteropParamException(pcsMarshal);
}
else if (SF_IsFieldGetterStub(m_dwStubFlags) || SF_IsFieldSetterStub(m_dwStubFlags))
{
// Field access stubs are not shared and do not use the secret parameter.
}
else if (SF_IsStructMarshalStub(m_dwStubFlags))
{
// Struct marshal stubs don't actually call anything so they do not need the secrect parameter.
}
else if (SF_IsForwardDelegateStub(m_dwStubFlags))
{
// Forward delegate stubs get all the context they need in 'this' so they
// don't use the secret parameter.
}
else
{
// All other IL stubs will need to use the secret parameter.
jitFlags.Set(CORJIT_FLAGS::CORJIT_FLAG_PUBLISH_SECRET_PARAM);
}
if (SF_IsReverseStub(m_dwStubFlags))
{
SwapStubSignatures(pStubMD);
}
ILCodeLabel* pTryBeginLabel = nullptr;
ILCodeLabel* pTryEndAndCatchBeginLabel = nullptr;
ILCodeLabel* pCatchEndLabel = nullptr;
if (hasTryCatchExceptionHandler)
{
EmitExceptionHandler(&nativeReturnType, &managedReturnType, &pTryBeginLabel, &pTryEndAndCatchBeginLabel, &pCatchEndLabel);
}
UINT maxStack;
size_t cbCode;
DWORD cbSig;
BYTE * pbBuffer;
BYTE * pbLocalSig;
cbCode = m_slIL.Link(&maxStack);
cbSig = m_slIL.GetLocalSigSize();
ILStubResolver * pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver();
COR_ILMETHOD_DECODER * pILHeader = pResolver->AllocGeneratedIL(cbCode, cbSig, maxStack);
pbBuffer = (BYTE *)pILHeader->Code;
pbLocalSig = (BYTE *)pILHeader->LocalVarSig;
_ASSERTE(cbSig == pILHeader->cbLocalVarSig);
ILStubEHClause cleanupTryFinally{};
m_slIL.GetCleanupFinallyOffsets(&cleanupTryFinally);
ILStubEHClause tryCatchClause{};
if (hasTryCatchExceptionHandler)
{
tryCatchClause.kind = ILStubEHClause::kTypedCatch;
tryCatchClause.dwTryBeginOffset = pTryBeginLabel != nullptr ? (DWORD)pTryBeginLabel->GetCodeOffset() : 0;
tryCatchClause.dwHandlerBeginOffset = ((DWORD)pTryEndAndCatchBeginLabel->GetCodeOffset());
tryCatchClause.cbTryLength = tryCatchClause.dwHandlerBeginOffset - tryCatchClause.dwTryBeginOffset;
tryCatchClause.cbHandlerLength = ((DWORD)pCatchEndLabel->GetCodeOffset()) - tryCatchClause.dwHandlerBeginOffset;
tryCatchClause.dwTypeToken = pcsMarshal->GetToken(g_pObjectClass);
}
int nEHClauses = 0;
if (tryCatchClause.cbHandlerLength != 0)
nEHClauses++;
if (cleanupTryFinally.cbHandlerLength != 0)
nEHClauses++;
if (nEHClauses > 0)
{
COR_ILMETHOD_SECT_EH* pEHSect = pResolver->AllocEHSect(nEHClauses);
PopulateEHSect(pEHSect, nEHClauses, &cleanupTryFinally, &tryCatchClause);
}
m_slIL.GenerateCode(pbBuffer, cbCode);
m_slIL.GetLocalSig(pbLocalSig, cbSig);
pResolver->SetJitFlags(jitFlags);
#ifdef LOGGING
LOG((LF_STUBS, LL_INFO1000, "---------------------------------------------------------------------\n"));
LOG((LF_STUBS, LL_INFO1000, "NDirect IL stub dump: %s::%s\n", pStubMD->m_pszDebugClassName, pStubMD->m_pszDebugMethodName));
if (LoggingEnabled() && LoggingOn(LF_STUBS, LL_INFO1000))
{
CQuickBytes qbManaged;
CQuickBytes qbLocal;
PCCOR_SIGNATURE pManagedSig;
ULONG cManagedSig;
IMDInternalImport* pIMDI = CoreLibBinder::GetModule()->GetMDImport();
pStubMD->GetSig(&pManagedSig, &cManagedSig);
PrettyPrintSig(pManagedSig, cManagedSig, "*", &qbManaged, pStubMD->GetMDImport(), NULL);
PrettyPrintSig(pbLocalSig, cbSig, NULL, &qbLocal, pIMDI, NULL);
LOG((LF_STUBS, LL_INFO1000, "incoming managed sig: %p: %s\n", pManagedSig, qbManaged.Ptr()));
LOG((LF_STUBS, LL_INFO1000, "locals sig: %p: %s\n", pbLocalSig+1, qbLocal.Ptr()));
if (cleanupTryFinally.cbHandlerLength != 0)
{
LOG((LF_STUBS, LL_INFO1000, "try_begin: 0x%04x try_end: 0x%04x finally_begin: 0x%04x finally_end: 0x%04x \n",
cleanupTryFinally.dwTryBeginOffset, cleanupTryFinally.dwTryBeginOffset + cleanupTryFinally.cbTryLength,
cleanupTryFinally.dwHandlerBeginOffset, cleanupTryFinally.dwHandlerBeginOffset + cleanupTryFinally.cbHandlerLength));
}
if (tryCatchClause.cbHandlerLength != 0)
{
LOG((LF_STUBS, LL_INFO1000, "try_begin: 0x%04x try_end: 0x%04x catch_begin: 0x%04x catch_end: 0x%04x type_token: 0x%08x\n",
tryCatchClause.dwTryBeginOffset, tryCatchClause.dwTryBeginOffset + tryCatchClause.cbTryLength,
tryCatchClause.dwHandlerBeginOffset, tryCatchClause.dwHandlerBeginOffset + tryCatchClause.cbHandlerLength,
tryCatchClause.dwTypeToken));
}
LogILStubFlags(LF_STUBS, LL_INFO1000, m_dwStubFlags);
m_slIL.LogILStub(jitFlags);
}
LOG((LF_STUBS, LL_INFO1000, "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"));
#endif // LOGGING
//
// Publish ETW events for IL stubs
//
// If the category and the event is enabled...
if (ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context, ILStubGenerated))
{
EtwOnILStubGenerated(
pStubMD,
pbLocalSig,
cbSig,
jitFlags,
&tryCatchClause,
&cleanupTryFinally,
maxStack,
(DWORD)cbCode
);
}
}
//
// Truncates a SString by first converting it to unicode and truncate it
// if it is larger than size. "..." will be appended if it is truncated.
//
void TruncateUnicodeString(SString &string, COUNT_T bufSize)
{
string.Normalize();
if ((string.GetCount() + 1) * sizeof(WCHAR) > bufSize)
{
_ASSERTE(bufSize / sizeof(WCHAR) > 4);
string.Truncate(string.Begin() + bufSize / sizeof(WCHAR) - 4);
string.Append(W("..."));
}
}
//---------------------------------------------------------------------------------------
//
void
EtwOnILStubGenerated(
MethodDesc * pStubMD,
PCCOR_SIGNATURE pbLocalSig,
DWORD cbSig,
CORJIT_FLAGS jitFlags,
ILStubEHClause * pConvertToHRTryCatchBounds,
ILStubEHClause * pCleanupTryFinallyBounds,
DWORD maxStack,
DWORD cbCode)
{
STANDARD_VM_CONTRACT;
//
// Interop Method Information
//
MethodDesc *pTargetMD = m_slIL.GetTargetMD();
SString strNamespaceOrClassName, strMethodName, strMethodSignature;
UINT64 uModuleId = 0;
if (pTargetMD)
{
pTargetMD->GetMethodInfoWithNewSig(strNamespaceOrClassName, strMethodName, strMethodSignature);
uModuleId = (UINT64)(TADDR)pTargetMD->GetModule();
}
//
// Stub Method Signature
//
SString stubNamespaceOrClassName, stubMethodName, stubMethodSignature;
pStubMD->GetMethodInfoWithNewSig(stubNamespaceOrClassName, stubMethodName, stubMethodSignature);
IMDInternalImport *pStubImport = pStubMD->GetModule()->GetMDImport();
CQuickBytes qbLocal;
PrettyPrintSig(pbLocalSig, (DWORD)cbSig, NULL, &qbLocal, pStubImport, NULL);
SString strLocalSig(SString::Utf8, (LPCUTF8)qbLocal.Ptr());
//
// Native Signature
//
SString strNativeSignature;
if (m_dwStubFlags & NDIRECTSTUB_FL_REVERSE_INTEROP)
{
// Reverse interop. Use StubSignature
strNativeSignature = stubMethodSignature;
}
else
{
// Forward interop. Use StubTarget signature
PCCOR_SIGNATURE pCallTargetSig = GetStubTargetMethodSig();
DWORD cCallTargetSig = GetStubTargetMethodSigLength();
CQuickBytes qbCallTargetSig;
PrettyPrintSig(pCallTargetSig, cCallTargetSig, "", &qbCallTargetSig, pStubImport, NULL);
strNativeSignature.SetUTF8((LPCUTF8)qbCallTargetSig.Ptr());
}
//
// Dump IL stub code
//
SString strILStubCode;
strILStubCode.Preallocate(4096); // Preallocate 4K bytes to avoid unnecessary growth
strILStubCode.AppendPrintf("// Code size\t%d (0x%04x)\n", cbCode, cbCode);
strILStubCode.AppendPrintf(".maxstack %d \n", maxStack);
strILStubCode.AppendPrintf(".locals %s\n", strLocalSig.GetUTF8());
m_slIL.LogILStub(jitFlags, &strILStubCode);
if (pConvertToHRTryCatchBounds->cbTryLength != 0 && pConvertToHRTryCatchBounds->cbHandlerLength != 0)
{
strILStubCode.AppendPrintf(
".try IL_%04x to IL_%04x catch handler IL_%04x to IL_%04x\n",
pConvertToHRTryCatchBounds->dwTryBeginOffset,
pConvertToHRTryCatchBounds->dwTryBeginOffset + pConvertToHRTryCatchBounds->cbTryLength,
pConvertToHRTryCatchBounds->dwHandlerBeginOffset,
pConvertToHRTryCatchBounds->dwHandlerBeginOffset + pConvertToHRTryCatchBounds->cbHandlerLength);
}
if (pCleanupTryFinallyBounds->cbTryLength != 0 && pCleanupTryFinallyBounds->cbHandlerLength != 0)
{
strILStubCode.AppendPrintf(
".try IL_%04x to IL_%04x finally handler IL_%04x to IL_%04x\n",
pCleanupTryFinallyBounds->dwTryBeginOffset,
pCleanupTryFinallyBounds->dwTryBeginOffset + pCleanupTryFinallyBounds->cbTryLength,
pCleanupTryFinallyBounds->dwHandlerBeginOffset,
pCleanupTryFinallyBounds->dwHandlerBeginOffset + pCleanupTryFinallyBounds->cbHandlerLength);
}
//
// Fire the event
//
DWORD dwFlags = 0;
if (m_dwStubFlags & NDIRECTSTUB_FL_REVERSE_INTEROP)
dwFlags |= ETW_IL_STUB_FLAGS_REVERSE_INTEROP;
#ifdef FEATURE_COMINTEROP
if (m_dwStubFlags & NDIRECTSTUB_FL_COM)
dwFlags |= ETW_IL_STUB_FLAGS_COM_INTEROP;
#endif // FEATURE_COMINTEROP
if (m_dwStubFlags & NDIRECTSTUB_FL_DELEGATE)
dwFlags |= ETW_IL_STUB_FLAGS_DELEGATE;
if (m_dwStubFlags & NDIRECTSTUB_FL_CONVSIGASVARARG)
dwFlags |= ETW_IL_STUB_FLAGS_VARARG;
if (m_dwStubFlags & NDIRECTSTUB_FL_UNMANAGED_CALLI)
dwFlags |= ETW_IL_STUB_FLAGS_UNMANAGED_CALLI;
if (m_dwStubFlags & NDIRECTSTUB_FL_STRUCT_MARSHAL)
dwFlags |= ETW_IL_STUB_FLAGS_STRUCT_MARSHAL;
DWORD dwToken = 0;
if (pTargetMD)
dwToken = pTargetMD->GetMemberDef();
//
// Truncate string fields. Make sure the whole event is less than 64KB
//
TruncateUnicodeString(strNamespaceOrClassName, ETW_IL_STUB_EVENT_STRING_FIELD_MAXSIZE);
TruncateUnicodeString(strMethodName, ETW_IL_STUB_EVENT_STRING_FIELD_MAXSIZE);
TruncateUnicodeString(strMethodSignature, ETW_IL_STUB_EVENT_STRING_FIELD_MAXSIZE);
TruncateUnicodeString(strNativeSignature, ETW_IL_STUB_EVENT_STRING_FIELD_MAXSIZE);
TruncateUnicodeString(stubMethodSignature, ETW_IL_STUB_EVENT_STRING_FIELD_MAXSIZE);
TruncateUnicodeString(strILStubCode, ETW_IL_STUB_EVENT_CODE_STRING_FIELD_MAXSIZE);
//
// Fire ETW event
//
FireEtwILStubGenerated(
GetClrInstanceId(), // ClrInstanceId
uModuleId, // ModuleIdentifier
(UINT64)pStubMD, // StubMethodIdentifier
dwFlags, // StubFlags
dwToken, // ManagedInteropMethodToken
strNamespaceOrClassName.GetUnicode(), // ManagedInteropMethodNamespace
strMethodName.GetUnicode(), // ManagedInteropMethodName
strMethodSignature.GetUnicode(), // ManagedInteropMethodSignature
strNativeSignature.GetUnicode(), // NativeSignature
stubMethodSignature.GetUnicode(), // StubMethodSigature
strILStubCode.GetUnicode() // StubMethodILCode
);
} // EtwOnILStubGenerated
#endif // DACCESS_COMPILE
#ifdef LOGGING
//---------------------------------------------------------------------------------------
//
static inline void LogOneFlag(DWORD flags, DWORD flag, LPCSTR str, DWORD facility, DWORD level)
{
LIMITED_METHOD_CONTRACT;
if (flags & flag)
{
LOG((facility, level, str));
}
}
static void LogILStubFlags(DWORD facility, DWORD level, DWORD dwStubFlags)
{
LIMITED_METHOD_CONTRACT;
LOG((facility, level, "dwStubFlags: 0x%08x\n", dwStubFlags));
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_CONVSIGASVARARG, " NDIRECTSTUB_FL_CONVSIGASVARARG\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_BESTFIT, " NDIRECTSTUB_FL_BESTFIT\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_THROWONUNMAPPABLECHAR, " NDIRECTSTUB_FL_THROWONUNMAPPABLECHAR\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_DELEGATE, " NDIRECTSTUB_FL_DELEGATE\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_DOHRESULTSWAPPING, " NDIRECTSTUB_FL_DOHRESULTSWAPPING\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_REVERSE_INTEROP, " NDIRECTSTUB_FL_REVERSE_INTEROP\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_STRUCT_MARSHAL, " NDIRECTSTUB_FL_STRUCT_MARSHAL\n", facility, level);
#ifdef FEATURE_COMINTEROP
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_COM, " NDIRECTSTUB_FL_COM\n", facility, level);
#endif // FEATURE_COMINTEROP
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_GENERATEDEBUGGABLEIL, " NDIRECTSTUB_FL_GENERATEDEBUGGABLEIL\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_UNMANAGED_CALLI, " NDIRECTSTUB_FL_UNMANAGED_CALLI\n", facility, level);
#ifdef FEATURE_COMINTEROP
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_FIELDGETTER, " NDIRECTSTUB_FL_FIELDGETTER\n", facility, level);
LogOneFlag(dwStubFlags, NDIRECTSTUB_FL_FIELDSETTER, " NDIRECTSTUB_FL_FIELDSETTER\n", facility, level);
#endif // FEATURE_COMINTEROP
//
// no need to log the internal flags, let's just assert what we expect to see...
//
CONSISTENCY_CHECK(!SF_IsCOMLateBoundStub(dwStubFlags));
CONSISTENCY_CHECK(!SF_IsCOMEventCallStub(dwStubFlags));
DWORD dwKnownMask =
NDIRECTSTUB_FL_CONVSIGASVARARG |
NDIRECTSTUB_FL_BESTFIT |
NDIRECTSTUB_FL_THROWONUNMAPPABLECHAR |
NDIRECTSTUB_FL_DELEGATE |
NDIRECTSTUB_FL_DOHRESULTSWAPPING |
NDIRECTSTUB_FL_REVERSE_INTEROP |
NDIRECTSTUB_FL_GENERATEDEBUGGABLEIL |
NDIRECTSTUB_FL_UNMANAGED_CALLI |
NDIRECTSTUB_FL_STRUCT_MARSHAL |
#ifdef FEATURE_COMINTEROP
NDIRECTSTUB_FL_COM |
NDIRECTSTUB_FL_COMLATEBOUND | // internal
NDIRECTSTUB_FL_COMEVENTCALL | // internal
NDIRECTSTUB_FL_FIELDGETTER |
NDIRECTSTUB_FL_FIELDSETTER |
#endif // FEATURE_COMINTEROP
0;
DWORD dwUnknownFlags = dwStubFlags & ~dwKnownMask;
if (0 != dwUnknownFlags)
{
LOG((facility, level, "UNKNOWN FLAGS: 0x%08x\n", dwUnknownFlags));
}
}
#endif // LOGGING
PCCOR_SIGNATURE GetStubTargetMethodSig()
{
CONTRACT(PCCOR_SIGNATURE)
{
STANDARD_VM_CHECK;
POSTCONDITION(CheckPointer(RETVAL, NULL_NOT_OK));
}
CONTRACT_END;
BYTE *pb;
if (!m_qbNativeFnSigBuffer.Size())
{
DWORD cb = m_slIL.GetStubTargetMethodSigSize();
pb = (BYTE *)m_qbNativeFnSigBuffer.AllocThrows(cb);
m_slIL.GetStubTargetMethodSig(pb, cb);
}
else
{
pb = (BYTE*)m_qbNativeFnSigBuffer.Ptr();
}
RETURN pb;
}
DWORD
GetStubTargetMethodSigLength()
{
WRAPPER_NO_CONTRACT;
return m_slIL.GetStubTargetMethodSigSize();
}
void SetStubTargetMethodSig(PCCOR_SIGNATURE pSig, DWORD cSig)
{
WRAPPER_NO_CONTRACT;
m_slIL.SetStubTargetMethodSig(pSig, cSig);
m_qbNativeFnSigBuffer.Shrink(0);
}
TokenLookupMap* GetTokenLookupMap() { WRAPPER_NO_CONTRACT; return m_slIL.GetTokenLookupMap(); }
DWORD GetFlags() const { return m_dwStubFlags; }
protected:
CQuickBytes m_qbNativeFnSigBuffer;
NDirectStubLinker m_slIL;
BOOL m_fSetLastError;
DWORD m_dwStubFlags;
};
class StructMarshal_ILStubState : public ILStubState
{
public:
StructMarshal_ILStubState(MethodTable* pMT, const Signature& signature, SigTypeContext* pTypeContext, DWORD dwStubFlags)
: ILStubState(
pMT->GetModule(),
signature,
pTypeContext,
dwStubFlags,
-1 /* We have no LCID parameter */,
nullptr),
m_nativeSize(pMT->GetNativeSize())
{
LIMITED_METHOD_CONTRACT;
}
void BeginEmit(DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
ILStubState::BeginEmit(dwStubFlags);
ILCodeStream* pcsSetup = m_slIL.GetSetupCodeStream();
ILCodeStream* pcsMarshal = m_slIL.GetMarshalCodeStream();
ILCodeStream* pcsUnmarshal = m_slIL.GetUnmarshalCodeStream();
ILCodeStream* pcsCleanup = m_slIL.GetCleanupCodeStream();
pMarshalStartLabel = pcsSetup->NewCodeLabel();
pCatchTrampolineStartLabel = pcsSetup->NewCodeLabel();
pCatchTrampolineEndLabel = pcsSetup->NewCodeLabel();
pUnmarshalStartLabel = pcsSetup->NewCodeLabel();
pCleanupStartLabel = pcsSetup->NewCodeLabel();
pReturnLabel = pcsSetup->NewCodeLabel();
dwExceptionDispatchInfoLocal = pcsSetup->NewLocal(CoreLibBinder::GetClass(CLASS__EXCEPTION_DISPATCH_INFO));
pcsSetup->EmitLDNULL();
pcsSetup->EmitSTLOC(dwExceptionDispatchInfoLocal);
pcsMarshal->EmitLabel(pMarshalStartLabel);
pcsUnmarshal->EmitLabel(pUnmarshalStartLabel);
pcsCleanup->EmitLabel(pCleanupStartLabel);
// Initialize the native structure's memory so we can do a partial cleanup
// if marshalling fails.
pcsMarshal->EmitLDARG(StructMarshalStubs::NATIVE_STRUCT_ARGIDX);
pcsMarshal->EmitLDC(0);
pcsMarshal->EmitLDC(m_nativeSize);
pcsMarshal->EmitINITBLK();
}
void FinishEmit(MethodDesc* pStubMD)
{
STANDARD_VM_CONTRACT;
ILCodeStream* pcsSetup = m_slIL.GetSetupCodeStream();
ILCodeStream* pcsMarshal = m_slIL.GetMarshalCodeStream();
ILCodeStream* pcsUnmarshal = m_slIL.GetUnmarshalCodeStream();
ILCodeStream* pcsDispatch = m_slIL.GetDispatchCodeStream();
ILCodeStream* pcsCleanup = m_slIL.GetCleanupCodeStream();
pcsSetup->EmitNOP("// marshal operation jump table {");
pcsSetup->EmitLDARG(StructMarshalStubs::OPERATION_ARGIDX);
pcsSetup->EmitLDC(StructMarshalStubs::MarshalOperation::Marshal);
pcsSetup->EmitBEQ(pMarshalStartLabel);
pcsSetup->EmitLDARG(StructMarshalStubs::OPERATION_ARGIDX);
pcsSetup->EmitLDC(StructMarshalStubs::MarshalOperation::Unmarshal);
pcsSetup->EmitBEQ(pUnmarshalStartLabel);
pcsSetup->EmitLDARG(StructMarshalStubs::OPERATION_ARGIDX);
pcsSetup->EmitLDC(StructMarshalStubs::MarshalOperation::Cleanup);
pcsSetup->EmitBEQ(pCleanupStartLabel);
pcsSetup->EmitNOP("// } marshal operation jump table");
// Clear native memory after release so we don't leave anything dangling.
pcsCleanup->EmitLDARG(StructMarshalStubs::NATIVE_STRUCT_ARGIDX);
pcsCleanup->EmitLDC(0);
pcsCleanup->EmitLDC(m_nativeSize);
pcsCleanup->EmitINITBLK();
pcsMarshal->EmitLEAVE(pReturnLabel);
pcsMarshal->EmitLabel(pCatchTrampolineStartLabel);
// WARNING: The ILStubLinker has no knowledge that the exception object is on the stack
// (because it is
// unaware that we've just entered a catch block), so we lie about the number of arguments
// (say the method takes one less) to rebalance the stack.
pcsMarshal->EmitCALL(METHOD__EXCEPTION_DISPATCH_INFO__CAPTURE, 0, 1);
pcsMarshal->EmitSTLOC(dwExceptionDispatchInfoLocal);
pcsMarshal->EmitLEAVE(pCleanupStartLabel);
pcsMarshal->EmitLabel(pCatchTrampolineEndLabel);
pcsDispatch->EmitLabel(pReturnLabel);
pcsDispatch->EmitRET();
pcsUnmarshal->EmitRET();
pcsCleanup->EmitLDLOC(dwExceptionDispatchInfoLocal);
pcsCleanup->EmitBRFALSE(pReturnLabel);
pcsCleanup->EmitLDLOC(dwExceptionDispatchInfoLocal);
pcsCleanup->EmitCALL(METHOD__EXCEPTION_DISPATCH_INFO__THROW, 0, 0);
pcsCleanup->EmitRET();
ILStubState::FinishEmit(pStubMD);
}
virtual void EmitExceptionHandler(LocalDesc* pNativeReturnType, LocalDesc* pManagedReturnType,
ILCodeLabel** ppTryBeginLabel, ILCodeLabel** ppTryEndCatchBeginLabel, ILCodeLabel** ppCatchEndLabel)
{
*ppTryBeginLabel = pMarshalStartLabel;
*ppTryEndCatchBeginLabel = pCatchTrampolineStartLabel;
*ppCatchEndLabel = pCatchTrampolineEndLabel;
}
private:
ILCodeLabel* pMarshalStartLabel = nullptr;
ILCodeLabel* pCatchTrampolineStartLabel = nullptr;
ILCodeLabel* pCatchTrampolineEndLabel = nullptr;
ILCodeLabel* pUnmarshalStartLabel = nullptr;
ILCodeLabel* pCleanupStartLabel = nullptr;
ILCodeLabel* pReturnLabel = nullptr;
DWORD dwExceptionDispatchInfoLocal;
UINT32 m_nativeSize;
};
class PInvoke_ILStubState : public ILStubState
{
public:
PInvoke_ILStubState(Module* pStubModule, const Signature &signature, SigTypeContext *pTypeContext, DWORD dwStubFlags,
CorInfoCallConvExtension unmgdCallConv, int iLCIDParamIdx, MethodDesc* pTargetMD)
: ILStubState(
pStubModule,
signature,
pTypeContext,
UpdateStubFlags(dwStubFlags, pTargetMD),
iLCIDParamIdx,
pTargetMD)
{
STANDARD_VM_CONTRACT;
m_slIL.SetCallingConvention(unmgdCallConv, SF_IsVarArgStub(dwStubFlags));
}
private:
static DWORD UpdateStubFlags(DWORD dwStubFlags, MethodDesc* pTargetMD)
{
if (TargetHasThis(dwStubFlags))
{
dwStubFlags |= NDIRECTSTUB_FL_TARGET_HAS_THIS;
}
if (StubHasThis(dwStubFlags))
{
dwStubFlags |= NDIRECTSTUB_FL_STUB_HAS_THIS;
}
if ((dwStubFlags & NDIRECTSTUB_FL_SUPPRESSGCTRANSITION) == 0
&& TargetSuppressGCTransition(dwStubFlags, pTargetMD))
{
dwStubFlags |= NDIRECTSTUB_FL_SUPPRESSGCTRANSITION;
}
if (HasCheckForPendingException(pTargetMD))
{
dwStubFlags |= NDIRECTSTUB_FL_CHECK_PENDING_EXCEPTION;
}
return dwStubFlags;
}
static BOOL TargetHasThis(DWORD dwStubFlags)
{
//
// in reverse pinvoke on delegate, the managed target will
// have a 'this' pointer, but the unmanaged signature does
// not.
//
return SF_IsReverseDelegateStub(dwStubFlags);
}
static BOOL StubHasThis(DWORD dwStubFlags)
{
//
// in forward pinvoke on a delegate, the stub will have a
// 'this' pointer, but the unmanaged target will not.
//
return SF_IsForwardDelegateStub(dwStubFlags);
}
static BOOL TargetSuppressGCTransition(DWORD dwStubFlags, MethodDesc* pTargetMD)
{
return SF_IsForwardStub(dwStubFlags) && pTargetMD && pTargetMD->ShouldSuppressGCTransition();
}
static BOOL HasCheckForPendingException(MethodDesc* pTargetMD)
{
if (pTargetMD == NULL || !pTargetMD->IsNDirect())
return FALSE;
auto pNMD = (NDirectMethodDesc*)pTargetMD;
if (!Interop::ShouldCheckForPendingException(pNMD))
return FALSE;
return TRUE;
}
};
#ifdef FEATURE_COMINTEROP
class CLRToCOM_ILStubState : public ILStubState
{
public:
CLRToCOM_ILStubState(Module* pStubModule, const Signature &signature, SigTypeContext *pTypeContext, DWORD dwStubFlags,
int iLCIDParamIdx, MethodDesc* pTargetMD)
: ILStubState(
pStubModule,
signature,
pTypeContext,
dwStubFlags | NDIRECTSTUB_FL_STUB_HAS_THIS | NDIRECTSTUB_FL_TARGET_HAS_THIS,
iLCIDParamIdx,
pTargetMD)
{
STANDARD_VM_CONTRACT;
if (SF_IsForwardStub(dwStubFlags))
{
m_slIL.SetCallingConvention(CorInfoCallConvExtension::Stdcall, SF_IsVarArgStub(dwStubFlags));
}
}
void BeginEmit(DWORD dwStubFlags) // CLR to COM IL
{
STANDARD_VM_CONTRACT;
ILStubState::BeginEmit(dwStubFlags);
ILCodeStream *pcsDispatch = m_slIL.GetDispatchCodeStream();
// add the 'this' COM IP parameter to the target CALLI
m_slIL.GetMarshalCodeStream()->SetStubTargetArgType(ELEMENT_TYPE_I, false);
// convert 'this' to COM IP and the target method entry point
m_slIL.EmitLoadRCWThis(pcsDispatch, m_dwStubFlags);
m_slIL.EmitLoadStubContext(pcsDispatch, dwStubFlags);
pcsDispatch->EmitLDLOCA(m_slIL.GetTargetEntryPointLocalNum());
DWORD dwIPRequiresCleanupLocalNum = pcsDispatch->NewLocal(ELEMENT_TYPE_BOOLEAN);
pcsDispatch->EmitLDLOCA(dwIPRequiresCleanupLocalNum);
// StubHelpers.GetCOMIPFromRCW(object objSrc, IntPtr pCPCMD, out IntPtr ppTarget, out bool pfNeedsRelease)
pcsDispatch->EmitCALL(METHOD__STUBHELPERS__GET_COM_IP_FROM_RCW, 4, 1);
// save it because we'll need it to compute the CALLI target and release it
pcsDispatch->EmitDUP();
pcsDispatch->EmitSTLOC(m_slIL.GetTargetInterfacePointerLocalNum());
// make sure it's Release()'ed after the call
m_slIL.SetCleanupNeeded();
ILCodeStream *pcsCleanup = m_slIL.GetCleanupCodeStream();
ILCodeLabel *pSkipThisCleanup = pcsCleanup->NewCodeLabel();
// and if it requires cleanup (i.e. it's not taken from the RCW cache)
pcsCleanup->EmitLDLOC(dwIPRequiresCleanupLocalNum);
pcsCleanup->EmitBRFALSE(pSkipThisCleanup);
pcsCleanup->EmitLDLOC(m_slIL.GetTargetInterfacePointerLocalNum());
pcsCleanup->EmitCALL(METHOD__INTERFACEMARSHALER__CLEAR_NATIVE, 1, 0);
pcsCleanup->EmitLabel(pSkipThisCleanup);
}
};
class COMToCLR_ILStubState : public ILStubState
{
public:
COMToCLR_ILStubState(Module* pStubModule, const Signature &signature, SigTypeContext *pTypeContext, DWORD dwStubFlags,
int iLCIDParamIdx, MethodDesc* pTargetMD)
: ILStubState(
pStubModule,
signature,
pTypeContext,
dwStubFlags | NDIRECTSTUB_FL_STUB_HAS_THIS | NDIRECTSTUB_FL_TARGET_HAS_THIS,
iLCIDParamIdx,
pTargetMD)
{
STANDARD_VM_CONTRACT;
}
void BeginEmit(DWORD dwStubFlags) // COM to CLR IL
{
STANDARD_VM_CONTRACT;
ILStubState::BeginEmit(dwStubFlags);
m_slIL.GetDispatchCodeStream()->EmitLoadThis();
}
};
class COMToCLRFieldAccess_ILStubState : public COMToCLR_ILStubState
{
public:
COMToCLRFieldAccess_ILStubState(Module* pStubModule, const Signature &signature, SigTypeContext *pTypeContext,
DWORD dwStubFlags, FieldDesc* pFD)
: COMToCLR_ILStubState(
pStubModule,
signature,
pTypeContext,
dwStubFlags,
-1,
NULL)
{
STANDARD_VM_CONTRACT;
_ASSERTE(pFD != NULL);
m_pFD = pFD;
}
void EmitInvokeTarget(MethodDesc *pStubMD)
{
STANDARD_VM_CONTRACT;
ILCodeStream* pcsDispatch = m_slIL.GetDispatchCodeStream();
if (SF_IsFieldGetterStub(m_dwStubFlags))
{
pcsDispatch->EmitLDFLD(pcsDispatch->GetToken(m_pFD));
}
else
{
CONSISTENCY_CHECK(SF_IsFieldSetterStub(m_dwStubFlags));
pcsDispatch->EmitSTFLD(pcsDispatch->GetToken(m_pFD));
}
}
protected:
FieldDesc *m_pFD;
};
#endif // FEATURE_COMINTEROP
ILStubLinkerFlags GetILStubLinkerFlagsForNDirectStubFlags(NDirectStubFlags flags)
{
DWORD result = ILSTUB_LINKER_FLAG_NONE;
if (!SF_IsCOMStub(flags))
{
result |= ILSTUB_LINKER_FLAG_NDIRECT;
}
if (SF_IsReverseStub(flags))
{
result |= ILSTUB_LINKER_FLAG_REVERSE;
}
if (flags & NDIRECTSTUB_FL_SUPPRESSGCTRANSITION)
{
result |= ILSTUB_LINKER_FLAG_SUPPRESSGCTRANSITION;
}
if (flags & NDIRECTSTUB_FL_STUB_HAS_THIS)
{
result |= ILSTUB_LINKER_FLAG_STUB_HAS_THIS;
}
if (flags & NDIRECTSTUB_FL_TARGET_HAS_THIS)
{
result |= ILSTUB_LINKER_FLAG_TARGET_HAS_THIS;
}
return (ILStubLinkerFlags)result;
}
NDirectStubLinker::NDirectStubLinker(
DWORD dwStubFlags,
Module* pModule,
const Signature &signature,
SigTypeContext *pTypeContext,
MethodDesc* pTargetMD,
int iLCIDParamIdx)
: ILStubLinker(pModule, signature, pTypeContext, pTargetMD, GetILStubLinkerFlagsForNDirectStubFlags((NDirectStubFlags)dwStubFlags)),
m_pCleanupFinallyBeginLabel(NULL),
m_pCleanupFinallyEndLabel(NULL),
m_pSkipExceptionCleanupLabel(NULL),
m_fHasCleanupCode(FALSE),
m_fHasExceptionCleanupCode(FALSE),
m_fCleanupWorkListIsSetup(FALSE),
m_targetHasThis((dwStubFlags & NDIRECTSTUB_FL_TARGET_HAS_THIS) != 0),
m_dwThreadLocalNum(-1),
m_dwCleanupWorkListLocalNum(-1),
m_dwRetValLocalNum(-1),
m_ErrorResID(-1),
m_ErrorParamIdx(-1),
m_iLCIDParamIdx(iLCIDParamIdx),
m_dwStubFlags(dwStubFlags)
{
STANDARD_VM_CONTRACT;
m_pcsSetup = NewCodeStream(ILStubLinker::kSetup); // do any one-time setup work
m_pcsMarshal = NewCodeStream(ILStubLinker::kMarshal); // marshals arguments
m_pcsDispatch = NewCodeStream(ILStubLinker::kDispatch); // sets up arguments and makes call
m_pcsRetUnmarshal = NewCodeStream(ILStubLinker::kReturnUnmarshal); // unmarshals return value
m_pcsUnmarshal = NewCodeStream(ILStubLinker::kUnmarshal); // unmarshals arguments
m_pcsExceptionCleanup = NewCodeStream(ILStubLinker::kExceptionCleanup); // MAY NOT THROW: goes in a finally and does exception-only cleanup
m_pcsCleanup = NewCodeStream(ILStubLinker::kCleanup); // MAY NOT THROW: goes in a finally and does unconditional cleanup
//
// Add locals
m_dwArgMarshalIndexLocalNum = NewLocal(ELEMENT_TYPE_I4);
m_pcsMarshal->EmitLDC(0);
m_pcsMarshal->EmitSTLOC(m_dwArgMarshalIndexLocalNum);
#ifdef FEATURE_COMINTEROP
//
// Forward COM interop needs a local to hold target interface pointer
//
if (SF_IsForwardCOMStub(m_dwStubFlags))
{
m_dwTargetEntryPointLocalNum = NewLocal(ELEMENT_TYPE_I);
m_dwTargetInterfacePointerLocalNum = NewLocal(ELEMENT_TYPE_I);
m_pcsSetup->EmitLoadNullPtr();
m_pcsSetup->EmitSTLOC(m_dwTargetInterfacePointerLocalNum);
}
#endif // FEATURE_COMINTEROP
#if defined(TARGET_X86) && defined(FEATURE_IJW)
m_dwCopyCtorChainLocalNum = (DWORD)-1;
#endif // defined(TARGET_X86) && defined(FEATURE_IJW)
}
void NDirectStubLinker::SetCallingConvention(CorInfoCallConvExtension unmngCallConv, BOOL fIsVarArg)
{
LIMITED_METHOD_CONTRACT;
#if !defined(TARGET_X86)
if (fIsVarArg)
{
// The JIT has to use a different calling convention for unmanaged vararg targets on 64-bit and ARM:
// any float values must be duplicated in the corresponding general-purpose registers.
SetStubTargetCallingConv(IMAGE_CEE_CS_CALLCONV_NATIVEVARARG);
}
else
#endif // !TARGET_X86
{
SetStubTargetCallingConv(unmngCallConv);
}
}
void NDirectStubLinker::EmitSetArgMarshalIndex(ILCodeStream* pcsEmit, UINT uArgIdx)
{
WRAPPER_NO_CONTRACT;
//
// This sets our state local variable that tracks the progress of the stub execution.
// In the finally block we test this variable to see what cleanup we need to do. The
// variable starts with the value of 0 and is assigned the following values as the
// stub executes:
//
// CLEANUP_INDEX_ARG0_MARSHAL + 1 - 1st argument marshaled
// CLEANUP_INDEX_ARG0_MARSHAL + 2 - 2nd argument marshaled
// ...
// CLEANUP_INDEX_ARG0_MARSHAL + n - nth argument marshaled
// CLEANUP_INDEX_RETVAL_UNMARSHAL + 1 - return value unmarshaled
// CLEANUP_INDEX_ARG0_UNMARSHAL + 1 - 1st argument unmarshaled
// CLEANUP_INDEX_ARG0_UNMARSHAL + 2 - 2nd argument unmarshaled
// ...
// CLEANUP_INDEX_ARG0_UNMARSHAL + n - nth argument unmarshaled
// CLEANUP_INDEX_ALL_DONE + 1 - ran to completion, no exception thrown
//
// Note: There may be gaps, i.e. if say 2nd argument does not need cleanup, the
// state variable will never be assigned the corresponding value. However, the
// value must always monotonically increase so we can use <=, >, etc.
//
pcsEmit->EmitLDC(uArgIdx + 1);
pcsEmit->EmitSTLOC(m_dwArgMarshalIndexLocalNum);
}
void NDirectStubLinker::EmitCheckForArgCleanup(ILCodeStream* pcsEmit, UINT uArgIdx, ArgCleanupBranchKind branchKind, ILCodeLabel* pSkipCleanupLabel)
{
STANDARD_VM_CONTRACT;
SetCleanupNeeded();
// See EmitSetArgMarshalIndex.
pcsEmit->EmitLDLOC(m_dwArgMarshalIndexLocalNum);
pcsEmit->EmitLDC(uArgIdx);
switch (branchKind)
{
case BranchIfMarshaled:
{
// we branch to the label if the argument has been marshaled
pcsEmit->EmitBGT(pSkipCleanupLabel);
break;
}
case BranchIfNotMarshaled:
{
// we branch to the label if the argument has not been marshaled
pcsEmit->EmitBLE(pSkipCleanupLabel);
break;
}
default:
UNREACHABLE();
}
}
int NDirectStubLinker::GetLCIDParamIdx()
{
LIMITED_METHOD_CONTRACT;
return m_iLCIDParamIdx;
}
ILCodeStream* NDirectStubLinker::GetSetupCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsSetup;
}
ILCodeStream* NDirectStubLinker::GetMarshalCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsMarshal;
}
ILCodeStream* NDirectStubLinker::GetUnmarshalCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsUnmarshal;
}
ILCodeStream* NDirectStubLinker::GetReturnUnmarshalCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsRetUnmarshal;
}
ILCodeStream* NDirectStubLinker::GetDispatchCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsDispatch;
}
ILCodeStream* NDirectStubLinker::GetCleanupCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsCleanup;
}
ILCodeStream* NDirectStubLinker::GetExceptionCleanupCodeStream()
{
LIMITED_METHOD_CONTRACT;
return m_pcsExceptionCleanup;
}
void NDirectStubLinker::SetInteropParamExceptionInfo(UINT resID, UINT paramIdx)
{
LIMITED_METHOD_CONTRACT;
// only keep the first one
if (HasInteropParamExceptionInfo())
{
return;
}
m_ErrorResID = resID;
m_ErrorParamIdx = paramIdx;
}
bool NDirectStubLinker::HasInteropParamExceptionInfo()
{
LIMITED_METHOD_CONTRACT;
return !(((DWORD)-1 == m_ErrorResID) && ((DWORD)-1 == m_ErrorParamIdx));
}
void NDirectStubLinker::GenerateInteropParamException(ILCodeStream* pcsEmit)
{
STANDARD_VM_CONTRACT;
pcsEmit->EmitLDC(m_ErrorResID);
pcsEmit->EmitLDC(m_ErrorParamIdx);
pcsEmit->EmitCALL(METHOD__STUBHELPERS__THROW_INTEROP_PARAM_EXCEPTION, 2, 0);
pcsEmit->EmitLDNULL();
pcsEmit->EmitTHROW();
}
#ifdef FEATURE_COMINTEROP
DWORD NDirectStubLinker::GetTargetInterfacePointerLocalNum()
{
LIMITED_METHOD_CONTRACT;
CONSISTENCY_CHECK(m_dwTargetInterfacePointerLocalNum != (DWORD)-1);
return m_dwTargetInterfacePointerLocalNum;
}
DWORD NDirectStubLinker::GetTargetEntryPointLocalNum()
{
LIMITED_METHOD_CONTRACT;
CONSISTENCY_CHECK(m_dwTargetEntryPointLocalNum != (DWORD)-1);
return m_dwTargetEntryPointLocalNum;
}
void NDirectStubLinker::EmitLoadRCWThis(ILCodeStream *pcsEmit, DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
pcsEmit->EmitLoadThis();
}
#endif // FEATURE_COMINTEROP
DWORD NDirectStubLinker::GetCleanupWorkListLocalNum()
{
LIMITED_METHOD_CONTRACT;
CONSISTENCY_CHECK(m_dwCleanupWorkListLocalNum != (DWORD)-1);
return m_dwCleanupWorkListLocalNum;
}
DWORD NDirectStubLinker::GetThreadLocalNum()
{
STANDARD_VM_CONTRACT;
if (m_dwThreadLocalNum == (DWORD)-1)
{
// The local is created and initialized lazily when first asked.
m_dwThreadLocalNum = NewLocal(ELEMENT_TYPE_I);
m_pcsSetup->EmitCALL(METHOD__THREAD__INTERNAL_GET_CURRENT_THREAD, 0, 1);
m_pcsSetup->EmitSTLOC(m_dwThreadLocalNum);
}
return m_dwThreadLocalNum;
}
DWORD NDirectStubLinker::GetReturnValueLocalNum()
{
LIMITED_METHOD_CONTRACT;
return m_dwRetValLocalNum;
}
#if defined(TARGET_X86) && defined(FEATURE_IJW)
DWORD NDirectStubLinker::GetCopyCtorChainLocalNum()
{
STANDARD_VM_CONTRACT;
if (m_dwCopyCtorChainLocalNum == (DWORD)-1)
{
// The local is created and initialized lazily when first asked.
m_dwCopyCtorChainLocalNum = NewLocal(CoreLibBinder::GetClass(CLASS__COPY_CONSTRUCTOR_CHAIN));
m_pcsSetup->EmitLDLOCA(m_dwCopyCtorChainLocalNum);
m_pcsSetup->EmitINITOBJ(m_pcsSetup->GetToken(CoreLibBinder::GetClass(CLASS__COPY_CONSTRUCTOR_CHAIN)));
}
return m_dwCopyCtorChainLocalNum;
}
#endif // defined(TARGET_X86) && defined(FEATURE_IJW)
BOOL NDirectStubLinker::IsCleanupNeeded()
{
LIMITED_METHOD_CONTRACT;
return (m_fHasCleanupCode || IsCleanupWorkListSetup());
}
BOOL NDirectStubLinker::IsExceptionCleanupNeeded()
{
LIMITED_METHOD_CONTRACT;
return m_fHasExceptionCleanupCode;
}
void NDirectStubLinker::InitCleanupCode()
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(NULL == m_pCleanupFinallyBeginLabel);
}
CONTRACTL_END;
m_pCleanupFinallyBeginLabel = NewCodeLabel();
m_pcsExceptionCleanup->EmitLabel(m_pCleanupFinallyBeginLabel);
}
void NDirectStubLinker::InitExceptionCleanupCode()
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(NULL == m_pSkipExceptionCleanupLabel);
}
CONTRACTL_END;
SetCleanupNeeded();
// we want to skip the entire exception cleanup if no exception has been thrown
m_pSkipExceptionCleanupLabel = NewCodeLabel();
EmitCheckForArgCleanup(m_pcsExceptionCleanup, CLEANUP_INDEX_ALL_DONE, BranchIfMarshaled, m_pSkipExceptionCleanupLabel);
}
void NDirectStubLinker::SetCleanupNeeded()
{
WRAPPER_NO_CONTRACT;
if (!m_fHasCleanupCode)
{
m_fHasCleanupCode = TRUE;
InitCleanupCode();
}
}
void NDirectStubLinker::SetExceptionCleanupNeeded()
{
WRAPPER_NO_CONTRACT;
if (!m_fHasExceptionCleanupCode)
{
m_fHasExceptionCleanupCode = TRUE;
InitExceptionCleanupCode();
}
}
void NDirectStubLinker::NeedsCleanupList()
{
STANDARD_VM_CONTRACT;
if (!IsCleanupWorkListSetup())
{
m_fCleanupWorkListIsSetup = TRUE;
SetCleanupNeeded();
// we setup a new local that will hold the cleanup work list
LocalDesc desc(CoreLibBinder::GetClass(CLASS__CLEANUP_WORK_LIST_ELEMENT));
m_dwCleanupWorkListLocalNum = NewLocal(desc);
}
}
BOOL NDirectStubLinker::IsCleanupWorkListSetup ()
{
LIMITED_METHOD_CONTRACT;
return m_fCleanupWorkListIsSetup;
}
void NDirectStubLinker::LoadCleanupWorkList(ILCodeStream* pcsEmit)
{
STANDARD_VM_CONTRACT;
if (SF_IsStructMarshalStub(m_dwStubFlags))
{
pcsEmit->EmitLDARG(StructMarshalStubs::CLEANUP_WORK_LIST_ARGIDX);
}
else
{
NeedsCleanupList();
pcsEmit->EmitLDLOCA(GetCleanupWorkListLocalNum());
}
}
void NDirectStubLinker::Begin(DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
if (!SF_IsForwardStub(dwStubFlags))
{
if (SF_IsDelegateStub(dwStubFlags))
{
//
// recover delegate object from UMEntryThunk
EmitLoadStubContext(m_pcsDispatch, dwStubFlags); // load UMEntryThunk*
m_pcsDispatch->EmitLDC(offsetof(UMEntryThunk, m_pObjectHandle));
m_pcsDispatch->EmitADD();
m_pcsDispatch->EmitLDIND_I(); // get OBJECTHANDLE
m_pcsDispatch->EmitLDIND_REF(); // get Delegate object
m_pcsDispatch->EmitLDFLD(GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__TARGET)));
}
}
m_pCleanupTryBeginLabel = NewCodeLabel();
m_pcsMarshal->EmitLabel(m_pCleanupTryBeginLabel);
}
void NDirectStubLinker::End(DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
ILCodeStream* pcs = m_pcsUnmarshal;
bool hasTryCatchForHRESULT = SF_IsReverseCOMStub(dwStubFlags)
&& !SF_IsFieldGetterStub(dwStubFlags)
&& !SF_IsFieldSetterStub(dwStubFlags);
//
// Create a local for the return value and store the return value in it.
//
if ((IsCleanupNeeded() || hasTryCatchForHRESULT) && !SF_IsStructMarshalStub(dwStubFlags))
{
// Save the return value if necessary, since the IL stack will be emptied when we leave a try block.
LocalDesc locDescRetVal;
if (SF_IsForwardStub(dwStubFlags))
{
GetStubReturnType(&locDescRetVal);
}
else
{
GetStubTargetReturnType(&locDescRetVal);
}
if (!( (locDescRetVal.cbType == 1) && (locDescRetVal.ElementType[0] == ELEMENT_TYPE_VOID) ))
{
m_dwRetValLocalNum = m_pcsRetUnmarshal->NewLocal(locDescRetVal);
if (SF_IsReverseStub(dwStubFlags) && StubHasVoidReturnType())
{
// if the target returns void and we are doing HRESULT swapping, S_OK is loaded
// in the unmarshal stream
m_pcsUnmarshal->EmitSTLOC(m_dwRetValLocalNum);
}
else
{
// otherwise the return value is loaded in the return unmarshal stream
m_pcsRetUnmarshal->EmitSTLOC(m_dwRetValLocalNum);
}
}
else if (hasTryCatchForHRESULT && (locDescRetVal.ElementType[0] != ELEMENT_TYPE_VOID))
{
m_dwRetValLocalNum = m_pcsRetUnmarshal->NewLocal(locDescRetVal);
}
}
//
// Emit end-of-try and end-of-finally code for the try/finally
//
if (IsCleanupNeeded() && !SF_IsStructMarshalStub(dwStubFlags))
{
m_pCleanupFinallyEndLabel = NewCodeLabel();
m_pCleanupTryEndLabel = NewCodeLabel();
if (IsExceptionCleanupNeeded())
{
// if we made it here, no exception has been thrown
EmitSetArgMarshalIndex(m_pcsUnmarshal, CLEANUP_INDEX_ALL_DONE);
}
// Emit a leave at the end of the try block. If we have an outer try/catch, we need
// to leave to the beginning of the ExceptionHandler code stream, which follows the
// Cleanup code stream. If we don't, we can just leave to the tail end of the
// Unmarshal code stream where we'll emit our RET.
ILCodeLabel* pLeaveTarget = m_pCleanupTryEndLabel;
if (hasTryCatchForHRESULT)
{
pLeaveTarget = m_pCleanupFinallyEndLabel;
}
m_pcsUnmarshal->EmitLEAVE(pLeaveTarget);
m_pcsUnmarshal->EmitLabel(m_pCleanupTryEndLabel);
// Emit a call to destroy the clean-up list if needed.
if (IsCleanupWorkListSetup())
{
LoadCleanupWorkList(m_pcsCleanup);
m_pcsCleanup->EmitCALL(METHOD__STUBHELPERS__DESTROY_CLEANUP_LIST, 1, 0);
}
// Emit the endfinally.
m_pcsCleanup->EmitENDFINALLY();
m_pcsCleanup->EmitLabel(m_pCleanupFinallyEndLabel);
}
if (IsExceptionCleanupNeeded())
{
m_pcsExceptionCleanup->EmitLabel(m_pSkipExceptionCleanupLabel);
}
// Reload the return value
if ((m_dwRetValLocalNum != (DWORD)-1) && !hasTryCatchForHRESULT && !SF_IsStructMarshalStub(dwStubFlags))
{
pcs->EmitLDLOC(m_dwRetValLocalNum);
}
}
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
EXTERN_C void STDCALL CopyConstructorCallStub(void);
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
void NDirectStubLinker::DoNDirect(ILCodeStream *pcsEmit, DWORD dwStubFlags, MethodDesc * pStubMD)
{
STANDARD_VM_CONTRACT;
if (SF_IsStructMarshalStub(dwStubFlags))
{
// Struct marshal stubs do not call anything, so this is a no-op
return;
}
if (SF_IsForwardStub(dwStubFlags)) // managed-to-native
{
if (SF_IsDelegateStub(dwStubFlags)) // delegate invocation
{
// get the delegate unmanaged target - we call a helper instead of just grabbing
// the _methodPtrAux field because we may need to intercept the call for host, etc.
pcsEmit->EmitLoadThis();
pcsEmit->EmitCALL(METHOD__STUBHELPERS__GET_DELEGATE_TARGET, 1, 1);
}
else // direct invocation
{
if (SF_IsCALLIStub(dwStubFlags)) // unmanaged CALLI
{
// for managed-to-unmanaged CALLI that requires marshaling, the target is passed
// as the secret argument to the stub by GenericPInvokeCalliHelper (asmhelpers.asm)
EmitLoadStubContext(pcsEmit, dwStubFlags);
#ifdef TARGET_64BIT
// the secret arg has been shifted to left and ORed with 1 (see code:GenericPInvokeCalliHelper)
pcsEmit->EmitLDC(1);
pcsEmit->EmitSHR_UN();
#endif
}
else
#ifdef FEATURE_COMINTEROP
if (!SF_IsCOMStub(dwStubFlags)) // forward P/Invoke
#endif // FEATURE_COMINTEROP
{
EmitLoadStubContext(pcsEmit, dwStubFlags);
pcsEmit->EmitLDC(offsetof(NDirectMethodDesc, ndirect.m_pNDirectTarget));
pcsEmit->EmitADD();
pcsEmit->EmitLDIND_I();
}
#ifdef FEATURE_COMINTEROP
else
{
// this is a CLR -> COM call
// the target has been computed by StubHelpers::GetCOMIPFromRCW
pcsEmit->EmitLDLOC(m_dwTargetEntryPointLocalNum);
}
#endif // FEATURE_COMINTEROP
}
}
else // native-to-managed
{
if (SF_IsDelegateStub(dwStubFlags)) // reverse P/Invoke via delegate
{
int tokDelegate_methodPtr = pcsEmit->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__METHOD_PTR));
EmitLoadStubContext(pcsEmit, dwStubFlags);
pcsEmit->EmitLDC(offsetof(UMEntryThunk, m_pObjectHandle));
pcsEmit->EmitADD();
pcsEmit->EmitLDIND_I(); // Get OBJECTHANDLE
pcsEmit->EmitLDIND_REF(); // Get Delegate object
pcsEmit->EmitLDFLD(tokDelegate_methodPtr); // get _methodPtr
}
#ifdef FEATURE_COMINTEROP
else if (SF_IsCOMStub(dwStubFlags)) // COM -> CLR call
{
// managed target is passed directly in the secret argument
EmitLoadStubContext(pcsEmit, dwStubFlags);
}
#endif // FEATURE_COMINTEROP
else // direct reverse P/Invoke (CoreCLR hosting)
{
EmitLoadStubContext(pcsEmit, dwStubFlags);
CONSISTENCY_CHECK(0 == offsetof(UMEntryThunk, m_pManagedTarget)); // if this changes, just add back the EmitLDC/EmitADD below
// pcsEmit->EmitLDC(offsetof(UMEntryThunk, m_pManagedTarget));
// pcsEmit->EmitADD();
pcsEmit->EmitLDIND_I(); // Get UMEntryThunk::m_pManagedTarget
}
}
#if defined(TARGET_X86) && defined(FEATURE_IJW)
if (m_dwCopyCtorChainLocalNum != (DWORD)-1)
{
// If we have a copy constructor chain local, we need to call the copy constructor stub
// to ensure that the chain is called correctly.
// Let's install the stub chain here and redirect the call to the stub.
DWORD targetLoc = NewLocal(ELEMENT_TYPE_I);
pcsEmit->EmitSTLOC(targetLoc);
pcsEmit->EmitLDLOCA(m_dwCopyCtorChainLocalNum);
pcsEmit->EmitLDLOC(targetLoc);
pcsEmit->EmitCALL(METHOD__COPY_CONSTRUCTOR_CHAIN__INSTALL, 2, 0);
pcsEmit->EmitLDC((DWORD_PTR)&CopyConstructorCallStub);
}
#endif // defined(TARGET_X86) && defined(FEATURE_IJW)
// For managed-to-native calls, the rest of the work is done by the JIT. It will
// erect InlinedCallFrame, flip GC mode, and use the specified calling convention
// to call the target. For native-to-managed calls, this is an ordinary managed
// CALLI and nothing special happens.
pcsEmit->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, 0, m_iTargetStackDelta);
}
void NDirectStubLinker::EmitLogNativeArgument(ILCodeStream* pslILEmit, DWORD dwPinnedLocal)
{
STANDARD_VM_CONTRACT;
if (SF_IsForwardPInvokeStub(m_dwStubFlags) && !SF_IsForwardDelegateStub(m_dwStubFlags))
{
// get the secret argument via intrinsic
pslILEmit->EmitCALL(METHOD__STUBHELPERS__GET_STUB_CONTEXT, 0, 1);
}
else
{
// no secret argument
pslILEmit->EmitLoadNullPtr();
}
pslILEmit->EmitLDLOC(dwPinnedLocal);
pslILEmit->EmitCALL(METHOD__STUBHELPERS__LOG_PINNED_ARGUMENT, 2, 0);
}
#ifndef DACCESS_COMPILE
void NDirectStubLinker::GetCleanupFinallyOffsets(ILStubEHClause * pClause)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pClause));
}
CONTRACTL_END;
if (m_pCleanupFinallyEndLabel)
{
_ASSERTE(m_pCleanupFinallyBeginLabel);
_ASSERTE(m_pCleanupTryBeginLabel);
_ASSERTE(m_pCleanupTryEndLabel);
pClause->kind = ILStubEHClause::kFinally;
pClause->dwTryBeginOffset = (DWORD)m_pCleanupTryBeginLabel->GetCodeOffset();
pClause->cbTryLength = (DWORD)m_pCleanupTryEndLabel->GetCodeOffset() - pClause->dwTryBeginOffset;
pClause->dwHandlerBeginOffset = (DWORD)m_pCleanupFinallyBeginLabel->GetCodeOffset();
pClause->cbHandlerLength = (DWORD)m_pCleanupFinallyEndLabel->GetCodeOffset() - pClause->dwHandlerBeginOffset;
}
}
#endif // DACCESS_COMPILE
void NDirectStubLinker::ClearCode()
{
WRAPPER_NO_CONTRACT;
ILStubLinker::ClearCode();
m_pCleanupTryBeginLabel = 0;
m_pCleanupTryEndLabel = 0;
m_pCleanupFinallyBeginLabel = 0;
m_pCleanupFinallyEndLabel = 0;
}
#ifdef PROFILING_SUPPORTED
DWORD NDirectStubLinker::EmitProfilerBeginTransitionCallback(ILCodeStream* pcsEmit, DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
if (SF_IsForwardDelegateStub(dwStubFlags) || SF_IsCALLIStub(dwStubFlags))
{
// secret argument does not contain MD nor UMEntryThunk
pcsEmit->EmitLoadNullPtr();
}
else
{
EmitLoadStubContext(pcsEmit, dwStubFlags);
}
if (SF_IsForwardStub(dwStubFlags))
{
pcsEmit->EmitLDLOC(GetThreadLocalNum());
}
else
{
// we use a null pThread to indicate reverse interop
pcsEmit->EmitLoadNullPtr();
}
// In the unmanaged delegate case, we need the "this" object to retrieve the MD
// in StubHelpers::ProfilerEnterCallback().
if (SF_IsDelegateStub(dwStubFlags))
{
_ASSERTE(SF_IsForwardStub(dwStubFlags));
pcsEmit->EmitLoadThis();
}
else
{
pcsEmit->EmitLDNULL();
}
pcsEmit->EmitCALL(METHOD__STUBHELPERS__PROFILER_BEGIN_TRANSITION_CALLBACK, 3, 1);
// Store the MD for StubHelpers::ProfilerLeaveCallback().
DWORD dwMethodDescLocalNum = pcsEmit->NewLocal(ELEMENT_TYPE_I);
pcsEmit->EmitSTLOC(dwMethodDescLocalNum);
return dwMethodDescLocalNum;
}
void NDirectStubLinker::EmitProfilerEndTransitionCallback(ILCodeStream* pcsEmit, DWORD dwStubFlags, DWORD dwMethodDescLocalNum)
{
STANDARD_VM_CONTRACT;
pcsEmit->EmitLDLOC(dwMethodDescLocalNum);
_ASSERTE(SF_IsForwardStub(dwStubFlags));
pcsEmit->EmitLDLOC(GetThreadLocalNum());
pcsEmit->EmitCALL(METHOD__STUBHELPERS__PROFILER_END_TRANSITION_CALLBACK, 2, 0);
}
#endif // PROFILING_SUPPPORTED
#ifdef VERIFY_HEAP
void NDirectStubLinker::EmitValidateLocal(ILCodeStream* pcsEmit, DWORD dwLocalNum, bool fIsByref, DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
pcsEmit->EmitLDLOC(dwLocalNum);
if (SF_IsDelegateStub(dwStubFlags))
{
pcsEmit->EmitLoadNullPtr();
pcsEmit->EmitLoadThis();
}
else if (SF_IsCALLIStub(dwStubFlags))
{
pcsEmit->EmitLoadNullPtr();
pcsEmit->EmitLDNULL();
}
else
{
// P/Invoke, CLR->COM
EmitLoadStubContext(pcsEmit, dwStubFlags);
pcsEmit->EmitLDNULL();
}
if (fIsByref)
{
// StubHelpers.ValidateByref(byref, pMD, pThis)
pcsEmit->EmitCALL(METHOD__STUBHELPERS__VALIDATE_BYREF, 3, 0);
}
else
{
// StubHelpers.ValidateObject(obj, pMD, pThis)
pcsEmit->EmitCALL(METHOD__STUBHELPERS__VALIDATE_OBJECT, 3, 0);
}
}
void NDirectStubLinker::EmitObjectValidation(ILCodeStream* pcsEmit, DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
// generate validation callouts for pinned locals
CQuickBytes qbLocalSig;
DWORD cbSig = GetLocalSigSize();
qbLocalSig.AllocThrows(cbSig);
PCOR_SIGNATURE pSig = (PCOR_SIGNATURE)qbLocalSig.Ptr();
GetLocalSig(pSig, cbSig);
SigPointer ptr(pSig, cbSig);
IfFailThrow(ptr.GetData(NULL)); // IMAGE_CEE_CS_CALLCONV_LOCAL_SIG
uint32_t numLocals;
IfFailThrow(ptr.GetData(&numLocals));
for (uint32_t i = 0; i < numLocals; i++)
{
BYTE modifier;
IfFailThrow(ptr.PeekByte(&modifier));
if (modifier == ELEMENT_TYPE_PINNED)
{
IfFailThrow(ptr.GetByte(NULL));
IfFailThrow(ptr.PeekByte(&modifier));
EmitValidateLocal(pcsEmit, i, (modifier == ELEMENT_TYPE_BYREF), dwStubFlags);
}
IfFailThrow(ptr.SkipExactlyOne());
}
}
#endif // VERIFY_HEAP
// Loads the 'secret argument' passed to the stub.
void NDirectStubLinker::EmitLoadStubContext(ILCodeStream* pcsEmit, DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
CONSISTENCY_CHECK(!SF_IsForwardDelegateStub(dwStubFlags));
CONSISTENCY_CHECK(!SF_IsFieldGetterStub(dwStubFlags) && !SF_IsFieldSetterStub(dwStubFlags));
// get the secret argument via intrinsic
pcsEmit->EmitCALL(METHOD__STUBHELPERS__GET_STUB_CONTEXT, 0, 1);
}
#ifdef FEATURE_COMINTEROP
class DispatchStubState : public StubState // For CLR-to-COM late-bound/eventing calls
{
public:
DispatchStubState()
: m_dwStubFlags(0),
m_lateBoundFlags(0)
{
WRAPPER_NO_CONTRACT;
}
void SetLastError(BOOL fSetLastError)
{
LIMITED_METHOD_CONTRACT;
CONSISTENCY_CHECK(!fSetLastError);
}
void BeginEmit(DWORD dwStubFlags)
{
LIMITED_METHOD_CONTRACT;
CONSISTENCY_CHECK(SF_IsCOMStub(dwStubFlags));
m_dwStubFlags = dwStubFlags;
}
void MarshalReturn(MarshalInfo* pInfo, int argOffset)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pInfo));
}
CONTRACTL_END;
}
void MarshalArgument(MarshalInfo* pInfo, int argOffset, UINT nativeStackOffset)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pInfo));
}
CONTRACTL_END;
if (SF_IsCOMLateBoundStub(m_dwStubFlags) && pInfo->GetDispWrapperType() != 0)
{
m_lateBoundFlags |= CLRToCOMCallInfo::kRequiresArgumentWrapping;
}
}
void MarshalLCID(int argIdx)
{
LIMITED_METHOD_CONTRACT;
}
void MarshalField(MarshalInfo* pInfo, UINT32 managedOffset, UINT32 nativeOffset, FieldDesc* pFieldDesc)
{
LIMITED_METHOD_CONTRACT;
UNREACHABLE();
}
#ifdef FEATURE_COMINTEROP
void MarshalHiddenLengthArgument(MarshalInfo *, BOOL)
{
LIMITED_METHOD_CONTRACT;
}
void MarshalFactoryReturn()
{
LIMITED_METHOD_CONTRACT;
UNREACHABLE();
}
#endif // FEATURE_COMINTEROP
void EmitInvokeTarget(MethodDesc *pStubMD)
{
LIMITED_METHOD_CONTRACT;
UNREACHABLE_MSG("Should never come to DispatchStubState::EmitInvokeTarget");
}
void FinishEmit(MethodDesc *pMD)
{
STANDARD_VM_CONTRACT;
// set flags directly on the interop MD
_ASSERTE(pMD->IsCLRToCOMCall());
((CLRToCOMCallMethodDesc *)pMD)->SetLateBoundFlags(m_lateBoundFlags);
}
protected:
DWORD m_dwStubFlags;
BYTE m_lateBoundFlags; // CLRToCOMCallMethodDesc::Flags
};
#endif // FEATURE_COMINTEROP
namespace
{
// Use CorInfoCallConvExtension::Managed as a sentinel represent a user-provided WinApi calling convention.
constexpr CorInfoCallConvExtension CallConvWinApiSentinel = CorInfoCallConvExtension::Managed;
// Returns the unmanaged calling convention for callConv or CallConvWinApiSentinel
// if the calling convention is not provided or WinApi.
CorInfoCallConvExtension GetCallConvValueForPInvokeCallConv(CorPinvokeMap callConv)
{
LIMITED_METHOD_CONTRACT;
switch (callConv)
{
case 0:
case pmCallConvWinapi:
return CallConvWinApiSentinel;
case pmCallConvCdecl:
return CorInfoCallConvExtension::C;
case pmCallConvStdcall:
return CorInfoCallConvExtension::Stdcall;
case pmCallConvThiscall:
return CorInfoCallConvExtension::Thiscall;
case pmCallConvFastcall:
return CorInfoCallConvExtension::Fastcall;
default:
_ASSERTE_MSG(false, "Invalid PInvoke callconv.");
return CallConvWinApiSentinel;
}
}
// Convert a CorNativeLinkType into an unambiguous usable value.
HRESULT RemapLinkType(_In_ CorNativeLinkType value, _Out_ CorNativeLinkType* nlt)
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(nlt != NULL);
// Handle case where the value is not in the defined enumeration.
if ((int)value == 0)
value = nltAnsi;
switch (value)
{
case nltAnsi:
*nlt = nltAnsi;
break;
case nltUnicode:
*nlt = nltUnicode;
break;
case nltAuto:
#ifdef TARGET_WINDOWS
*nlt = nltUnicode;
#else
*nlt = nltAnsi; // We don't have a utf8 charset in metadata so ANSI == UTF-8 off-Windows
#endif
break;
default:
return E_INVALIDARG;
}
// Validate we remapped to a usable value.
_ASSERTE(*nlt == nltAnsi || *nlt == nltUnicode);
return S_OK;
}
HRESULT ParseCallingConventionFromAttributeConstructor(_Inout_ CustomAttributeParser& ca, _Out_ CorInfoCallConvExtension* callConv)
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(callConv != NULL);
CaArg callConvArg;
callConvArg.InitEnum(SERIALIZATION_TYPE_I4, (ULONG)0);
HRESULT hr = ParseKnownCaArgs(ca, &callConvArg, 1);
if (FAILED(hr))
return hr;
*callConv = GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(callConvArg.val.u4 << 8));
return S_OK;
}
}
void PInvokeStaticSigInfo::PreInit(Module* pModule, MethodTable * pMT)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// initialize data members
m_wFlags = 0;
m_pModule = pModule;
m_callConv = CallConvWinApiSentinel;
SetBestFitMapping (TRUE);
SetThrowOnUnmappableChar (FALSE);
SetLinkFlags (nlfNone);
SetCharSet (nltAnsi);
// assembly/type level m_bestFit & m_bThrowOnUnmappableChar
BOOL bBestFit;
BOOL bThrowOnUnmappableChar;
if (pMT != NULL)
{
EEClass::GetBestFitMapping(pMT, &bBestFit, &bThrowOnUnmappableChar);
}
else
{
ReadBestFitCustomAttribute(m_pModule, mdTypeDefNil, &bBestFit, &bThrowOnUnmappableChar);
}
SetBestFitMapping (bBestFit);
SetThrowOnUnmappableChar (bThrowOnUnmappableChar);
}
void PInvokeStaticSigInfo::PreInit(MethodDesc* pMD)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
PreInit(pMD->GetModule(), pMD->GetMethodTable());
m_sig = pMD->GetSignature();
if (pMD->IsEEImpl())
{
CONSISTENCY_CHECK(pMD->GetMethodTable()->IsDelegate());
SetIsDelegateInterop(TRUE);
}
}
PInvokeStaticSigInfo::PInvokeStaticSigInfo(
_In_ MethodDesc* pMD,
_Outptr_opt_ LPCUTF8* pLibName,
_Outptr_opt_ LPCUTF8* pEntryPointName)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
DllImportInit(pMD, pLibName, pEntryPointName);
}
PInvokeStaticSigInfo::PInvokeStaticSigInfo(_In_ MethodDesc* pMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
HRESULT hr = S_OK;
MethodTable * pMT = pMD->GetMethodTable();
if (!pMT->IsDelegate())
{
DllImportInit(pMD, NULL, NULL);
return;
}
// initialize data members to defaults
PreInit(pMD);
// System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute
BYTE* pData = NULL;
LONG cData = 0;
CorInfoCallConvExtension callConv = CallConvWinApiSentinel;
hr = pMT->GetCustomAttribute(
WellKnownAttribute::UnmanagedFunctionPointer, (const VOID **)(&pData), (ULONG *)&cData);
IfFailGo(hr);
if (cData != 0)
{
CustomAttributeParser ca(pData, cData);
IfFailGo(ParseCallingConventionFromAttributeConstructor(ca, &callConv));
enum UnmanagedFunctionPointerNamedArgs
{
MDA_CharSet,
MDA_BestFitMapping,
MDA_ThrowOnUnmappableChar,
MDA_SetLastError,
MDA_Last,
};
CaNamedArg namedArgs[MDA_Last];
namedArgs[MDA_CharSet].InitI4FieldEnum("CharSet", "System.Runtime.InteropServices.CharSet", (ULONG)GetCharSet());
namedArgs[MDA_BestFitMapping].InitBoolField("BestFitMapping", (ULONG)GetBestFitMapping());
namedArgs[MDA_ThrowOnUnmappableChar].InitBoolField("ThrowOnUnmappableChar", (ULONG)GetThrowOnUnmappableChar());
namedArgs[MDA_SetLastError].InitBoolField("SetLastError", 0);
IfFailGo(ParseKnownCaNamedArgs(ca, namedArgs, ARRAY_SIZE(namedArgs)));
CorNativeLinkType nlt;
IfFailGo(RemapLinkType((CorNativeLinkType)namedArgs[MDA_CharSet].val.u4, &nlt));
SetCharSet ( nlt );
SetBestFitMapping (namedArgs[MDA_BestFitMapping].val.u1);
SetThrowOnUnmappableChar (namedArgs[MDA_ThrowOnUnmappableChar].val.u1);
if (namedArgs[MDA_SetLastError].val.u1)
SetLinkFlags ((CorNativeLinkFlags)(nlfLastError | GetLinkFlags()));
}
InitCallConv(callConv, pMD->IsVarArg());
ErrExit:
if (FAILED(hr))
ThrowError(IDS_EE_NDIRECT_BADNATL);
}
PInvokeStaticSigInfo::PInvokeStaticSigInfo(
_In_ const Signature& sig, _In_ Module* pModule)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pModule));
}
CONTRACTL_END;
PreInit(pModule, NULL);
m_sig = sig;
InitCallConv(CallConvWinApiSentinel, FALSE);
}
void PInvokeStaticSigInfo::DllImportInit(
_In_ MethodDesc* pMD,
_Outptr_opt_ LPCUTF8* ppLibName,
_Outptr_opt_ LPCUTF8* ppEntryPointName)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
// These preconditions to prevent multithreaded regression
// where pMD->ndirect.m_szLibName was passed in directly, cleared
// by this API, then accessed on another thread before being reset here.
PRECONDITION(CheckPointer(ppLibName, NULL_OK) && (!ppLibName || *ppLibName == NULL));
PRECONDITION(CheckPointer(ppEntryPointName, NULL_OK) && (!ppEntryPointName || *ppEntryPointName == NULL));
}
CONTRACTL_END;
// initialize data members to defaults
PreInit(pMD);
// System.Runtime.InteropServices.DllImportAttribute
IMDInternalImport *pInternalImport = pMD->GetMDImport();
CorPinvokeMap mappingFlags = pmMaxValue;
mdModuleRef modref = mdModuleRefNil;
if (FAILED(pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, ppEntryPointName, &modref)))
{
InitCallConv(CallConvWinApiSentinel, pMD);
return;
}
if (ppEntryPointName && *ppEntryPointName == NULL)
*ppEntryPointName = pMD->GetName();
// out parameter pLibName
if (ppLibName != NULL)
{
if (FAILED(pInternalImport->GetModuleRefProps(modref, ppLibName)))
{
ThrowError(IDS_CLASSLOAD_BADFORMAT);
}
}
// m_callConv
InitCallConv(GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask)), pMD);
// m_bestFit
CorPinvokeMap bestFitMask = (CorPinvokeMap)(mappingFlags & pmBestFitMask);
if (bestFitMask == pmBestFitEnabled)
SetBestFitMapping (TRUE);
else if (bestFitMask == pmBestFitDisabled)
SetBestFitMapping (FALSE);
// m_bThrowOnUnmappableChar
CorPinvokeMap unmappableMask = (CorPinvokeMap)(mappingFlags & pmThrowOnUnmappableCharMask);
if (unmappableMask == pmThrowOnUnmappableCharEnabled)
SetThrowOnUnmappableChar (TRUE);
else if (unmappableMask == pmThrowOnUnmappableCharDisabled)
SetThrowOnUnmappableChar (FALSE);
// linkFlags : CorPinvoke -> CorNativeLinkFlags
if (mappingFlags & pmSupportsLastError)
SetLinkFlags ((CorNativeLinkFlags)(GetLinkFlags() | nlfLastError));
if (mappingFlags & pmNoMangle)
SetLinkFlags ((CorNativeLinkFlags)(GetLinkFlags() | nlfNoMangle));
// charset : CorPinvoke -> CorNativeLinkType
CorPinvokeMap charSetMask = (CorPinvokeMap)(mappingFlags & (pmCharSetNotSpec | pmCharSetAnsi | pmCharSetUnicode | pmCharSetAuto));
CorNativeLinkType nlt = nltMaxValue; // Initialize to invalid value
switch (charSetMask)
{
case pmCharSetNotSpec:
case pmCharSetAnsi:
nlt = nltAnsi;
break;
case pmCharSetUnicode:
nlt = nltUnicode;
break;
case pmCharSetAuto:
nlt = nltAuto;
break;
default:
_ASSERTE("Unknown CharSet mask value");
break;
}
if (FAILED(RemapLinkType(nlt, &nlt)))
ThrowError(IDS_EE_NDIRECT_BADNATL);
SetCharSet(nlt);
}
// This function would work, but be unused on Unix. Ifdefing out to avoid build errors due to the unused function.
#if !defined (TARGET_UNIX)
static LPBYTE FollowIndirect(LPBYTE pTarget)
{
CONTRACT(LPBYTE)
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
LPBYTE pRet = NULL;
EX_TRY
{
AVInRuntimeImplOkayHolder AVOkay;
#ifdef TARGET_X86
if (pTarget != NULL && !(pTarget[0] != 0xff || pTarget[1] != 0x25))
{
pRet = **(LPBYTE**)(pTarget + 2);
}
#elif defined(TARGET_AMD64)
if (pTarget != NULL && !(pTarget[0] != 0xff || pTarget[1] != 0x25))
{
INT64 rva = *(INT32*)(pTarget + 2);
pRet = *(LPBYTE*)(pTarget + 6 + rva);
}
#endif
}
EX_CATCH
{
// Catch AVs here.
}
EX_END_CATCH(SwallowAllExceptions);
RETURN pRet;
}
#endif // !TARGET_UNIX
#ifdef FEATURE_IJW
BOOL HeuristicDoesThisLookLikeAGetLastErrorCall(LPBYTE pTarget)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
static LPBYTE pGetLastError = NULL;
if (!pGetLastError)
{
// No need to use a holder here, since no cleanup is necessary.
HMODULE hMod = GetModuleHandle(WINDOWS_KERNEL32_DLLNAME_W);
if (hMod)
{
pGetLastError = (LPBYTE)GetProcAddress(hMod, "GetLastError");
if (!pGetLastError)
{
// This should never happen but better to be cautious.
pGetLastError = (LPBYTE)-1;
}
}
else
{
// We failed to get the module handle for kernel32.dll. This is almost impossible
// however better to err on the side of caution.
pGetLastError = (LPBYTE)-1;
}
}
if (pTarget == pGetLastError)
return TRUE;
if (pTarget == NULL)
return FALSE;
LPBYTE pTarget2 = FollowIndirect(pTarget);
if (pTarget2)
{
// jmp [xxxx] - could be an import thunk
return pTarget2 == pGetLastError;
}
return FALSE;
}
#endif // FEATURE_IJW
CorInfoCallConvExtension GetDefaultCallConv(BOOL bIsVarArg)
{
return bIsVarArg ? CorInfoCallConvExtension::C : CallConv::GetDefaultUnmanagedCallingConvention();
}
void PInvokeStaticSigInfo::InitCallConv(_In_ CorInfoCallConvExtension callConv, _In_ MethodDesc *pMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pMD != NULL);
}
CONTRACTL_END;
// If the calling convention has not been determined yet, check the UnmanagedCallConv attribute
if (callConv == CallConvWinApiSentinel)
{
CallConvBuilder builder;
UINT errorResID = 0;
// System.Runtime.InteropServices.UnmanagedCallConvAttribute
HRESULT hr = CallConv::TryGetCallingConventionFromUnmanagedCallConv(pMD, &builder, &errorResID);
if (FAILED(hr))
{
// Use a generic error message for P/Invokes or UnmanagedFunction if no specific one was provided
ThrowError(errorResID == 0 ? IDS_EE_NDIRECT_BADNATL : errorResID);
}
if (hr == S_OK)
{
callConv = builder.GetCurrentCallConv();
if (builder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION))
{
SetShouldSuppressGCTransition(TRUE);
}
}
}
InitCallConv(callConv, pMD->IsVarArg());
}
void PInvokeStaticSigInfo::InitCallConv(CorInfoCallConvExtension callConv, BOOL bIsVarArg)
{
STANDARD_VM_CONTRACT;
CallConvBuilder builder;
UINT errorResID;
HRESULT hr = CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(m_pModule), m_sig.GetRawSig(), m_sig.GetRawSigLen(), &builder, &errorResID);
if (FAILED(hr))
{
// Use an error message specific to P/Invokes or UnmanagedFunction for bad format.
ThrowError(hr == COR_E_BADIMAGEFORMAT ? IDS_EE_NDIRECT_BADNATL : errorResID);
}
CorInfoCallConvExtension sigCallConv = builder.GetCurrentCallConv();
// Validate that either no specific calling convention is provided or that the signature calling convention
// matches the DllImport calling convention.
// If no calling convention is provided, then use the default calling convention for the platform.
if (callConv != CallConvWinApiSentinel && sigCallConv != CallConvWinApiSentinel && callConv != sigCallConv)
ThrowError(IDS_EE_NDIRECT_BADNATL_CALLCONV);
if (callConv == CallConvWinApiSentinel && sigCallConv == CallConvWinApiSentinel)
m_callConv = GetDefaultCallConv(bIsVarArg);
else if (callConv != CallConvWinApiSentinel)
m_callConv = callConv;
else
m_callConv = sigCallConv;
if (bIsVarArg && m_callConv != CorInfoCallConvExtension::C)
ThrowError(IDS_EE_NDIRECT_BADNATL_VARARGS_CALLCONV);
_ASSERTE(m_callConv != CallConvWinApiSentinel);
}
void PInvokeStaticSigInfo::ThrowError(UINT errorResourceID)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(errorResourceID != 0);
}
CONTRACTL_END;
COMPlusThrow(kTypeLoadException, errorResourceID);
}
namespace
{
bool HasSuppressGCTransitionAttribute(_In_ MethodDesc* pMD)
{
LIMITED_METHOD_CONTRACT;
HRESULT hr = pMD->GetCustomAttribute(
WellKnownAttribute::SuppressGCTransition,
nullptr,
nullptr);
return hr == S_OK;
}
bool TryGetCallingConventionFromPInvokeMetadata(_In_ MethodDesc* pMD, _Out_ CorInfoCallConvExtension* callConv)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pMD != NULL);
PRECONDITION(pMD->IsNDirect());
PRECONDITION(callConv != NULL);
}
CONTRACTL_END;
CorInfoCallConvExtension callConvLocal;
IMDInternalImport* pInternalImport = pMD->GetMDImport();
CorPinvokeMap mappingFlags = pmMaxValue;
HRESULT hr = pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, NULL /*pszImportName*/, NULL /*pmrImportDLL*/);
if (FAILED(hr))
return false;
callConvLocal = GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask));
if (callConvLocal != CallConvWinApiSentinel)
{
*callConv = callConvLocal;
return true;
}
return false;
}
bool TryGetCallingConventionFromUnmanagedFunctionPointer(_In_ MethodTable* pMT, _Out_ CorInfoCallConvExtension* callConv)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pMT != NULL);
PRECONDITION(pMT->IsDelegate());
PRECONDITION(callConv != NULL);
}
CONTRACTL_END;
BYTE* pData = NULL;
LONG cData = 0;
HRESULT hr = pMT->GetCustomAttribute(WellKnownAttribute::UnmanagedFunctionPointer, (const VOID **)(&pData), (ULONG *)&cData);
if (hr != S_OK)
return false;
_ASSERTE(cData > 0);
CustomAttributeParser ca(pData, cData);
CorInfoCallConvExtension callConvLocal;
hr = ParseCallingConventionFromAttributeConstructor(ca, &callConvLocal);
if (SUCCEEDED(hr) && callConvLocal != CallConvWinApiSentinel)
{
*callConv = callConvLocal;
return true;
}
return false;
}
}
void NDirect::GetCallingConvention_IgnoreErrors(_In_ MethodDesc* pMD, _Out_opt_ CorInfoCallConvExtension* callConv, _Out_opt_ bool* suppressGCTransition)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pMD != NULL);
PRECONDITION(pMD->IsNDirect());
PRECONDITION(callConv != NULL || suppressGCTransition != NULL);
}
CONTRACTL_END;
if (suppressGCTransition != NULL)
{
*suppressGCTransition = HasSuppressGCTransitionAttribute(pMD);
// Caller only cares about SuppressGCTransition and we have already determined it is true.
if (callConv == NULL && *suppressGCTransition)
return;
}
// This method intentionally does not check that any calling convention specified through
// attributes match that in the signature. We just return once a non-sentinel calling
// convention is found.
CorInfoCallConvExtension callConvLocal;
MethodTable* pMT = pMD->GetMethodTable();
if (pMT->IsDelegate())
{
if (callConv == NULL)
return;
// System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute
if (TryGetCallingConventionFromUnmanagedFunctionPointer(pMT, &callConvLocal))
{
*callConv = callConvLocal;
return;
}
}
else
{
// P/Invoke metadata
if (TryGetCallingConventionFromPInvokeMetadata(pMD, &callConvLocal))
{
if (callConv != NULL)
*callConv = callConvLocal;
return;
}
// System.Runtime.InteropServices.UnmanagedCallConvAttribute
CallConvBuilder unmanagedCallConvBuilder;
if (CallConv::TryGetCallingConventionFromUnmanagedCallConv(pMD, &unmanagedCallConvBuilder, NULL /*errorResID*/) == S_OK)
{
if (suppressGCTransition != NULL)
{
(*suppressGCTransition) |= unmanagedCallConvBuilder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION);
}
callConvLocal = unmanagedCallConvBuilder.GetCurrentCallConv();
if (callConvLocal != CallConvWinApiSentinel)
{
if (callConv != NULL)
*callConv = callConvLocal;
return;
}
}
// Caller only cares about SuppressGCTransition - we have checked SuppressGCTransition and UnmanagedCallConv
if (callConv == NULL)
return;
}
_ASSERTE(callConv != NULL);
const Signature& sig = pMD->GetSignature();
Module* module = pMD->GetModule();
CallConvBuilder builder;
UINT errorResID;
// modopts
(void)CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(module), sig.GetRawSig(), sig.GetRawSigLen(), &builder, &errorResID);
callConvLocal = builder.GetCurrentCallConv();
if (callConvLocal != CallConvWinApiSentinel)
{
*callConv = callConvLocal;
return;
}
*callConv = GetDefaultCallConv(pMD->IsVarArg());
}
//---------------------------------------------------------
// Does a class or method have a NAT_L CustomAttribute?
//
// S_OK = yes
// S_FALSE = no
// FAILED = unknown because something failed.
//---------------------------------------------------------
/*static*/
HRESULT NDirect::HasNAT_LAttribute(IMDInternalImport *pInternalImport, mdToken token, DWORD dwMemberAttrs)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
PRECONDITION(CheckPointer(pInternalImport));
PRECONDITION(TypeFromToken(token) == mdtMethodDef);
}
CONTRACTL_END;
// Check method flags first before trying to find the custom value
if (!IsReallyMdPinvokeImpl(dwMemberAttrs))
return S_FALSE;
DWORD mappingFlags;
LPCSTR pszImportName;
mdModuleRef modref;
if (SUCCEEDED(pInternalImport->GetPinvokeMap(token, &mappingFlags, &pszImportName, &modref)))
return S_OK;
return S_FALSE;
}
// Either MD or signature & module must be given.
/*static*/
BOOL NDirect::MarshalingRequired(
_In_opt_ MethodDesc* pMD,
_In_opt_ PCCOR_SIGNATURE pSig,
_In_opt_ Module* pModule,
_In_opt_ SigTypeContext* pTypeContext,
_In_ bool unmanagedCallersOnlyRequiresMarshalling)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pMD != NULL || (pSig != NULL && pModule != NULL));
}
CONTRACTL_END;
// As a by-product, when returning FALSE we will also set the native stack size to the MD if it's
// an NDirectMethodDesc. This number is needed to link the P/Invoke (it determines the @n entry
// point name suffix and affects alignment thunk generation on the Mac). If this method returns
// TRUE, the stack size will be set when building the marshaling IL stub.
DWORD dwStackSize = 0;
CorInfoCallConvExtension callConv = CallConv::GetDefaultUnmanagedCallingConvention();
if (pMD != NULL)
{
// HRESULT swapping is handled by stub
if (pMD->IsNDirect() || pMD->IsCLRToCOMCall())
{
if (!IsMiPreserveSig(pMD->GetImplAttrs()))
return TRUE;
}
PInvokeStaticSigInfo sigInfo;
if (!pMD->IsNDirect())
{
new (&sigInfo) PInvokeStaticSigInfo(pMD);
}
else
{
// A P/Invoke marked with UnmanagedCallersOnlyAttribute
// doesn't technically require marshalling. However, we
// don't support a DllImport with this attribute and we
// error out during IL Stub generation so we indicate that
// when checking if an IL Stub is needed.
//
// Callers can indicate the check doesn't need to be performed.
if (unmanagedCallersOnlyRequiresMarshalling && pMD->HasUnmanagedCallersOnlyAttribute())
return TRUE;
NDirectMethodDesc* pNMD = (NDirectMethodDesc*)pMD;
InitializeSigInfoAndPopulateNDirectMethodDesc(pNMD, &sigInfo);
// Pending exceptions are handled by stub
if (Interop::ShouldCheckForPendingException(pNMD))
return TRUE;
}
// SetLastError is handled by stub
if (sigInfo.GetLinkFlags() & nlfLastError)
return TRUE;
// LCID argument is handled by stub
if (GetLCIDParameterIndex(pMD) != -1)
return TRUE;
callConv = sigInfo.GetCallConv();
}
if (pSig == NULL)
{
PREFIX_ASSUME(pMD != NULL);
pSig = pMD->GetSig();
pModule = pMD->GetModule();
}
// Check to make certain that the signature only contains types that marshal trivially
SigPointer ptr(pSig);
IfFailThrow(ptr.GetCallingConvInfo(NULL));
uint32_t numArgs;
IfFailThrow(ptr.GetData(&numArgs));
numArgs++; // +1 for return type
// We'll need to parse parameter native types
mdParamDef *pParamTokenArray = (mdParamDef *)_alloca(numArgs * sizeof(mdParamDef));
IMDInternalImport *pMDImport = pModule->GetMDImport();
mdMethodDef methodToken = mdMethodDefNil;
if (pMD != NULL)
{
methodToken = pMD->GetMemberDef();
}
CollateParamTokens(pMDImport, methodToken, numArgs - 1, pParamTokenArray);
// We enable the runtime marshalling system whenever it is enabled on the module as a whole
// or when the call is a COM interop call. COM interop calls are already using a significant portion of the runtime
// marshalling system just to function at all, so we aren't going to disable the parameter marshalling;
// we'd rather have developers use the feature flag to diable the whole COM interop subsystem at once.
bool runtimeMarshallingEnabled = pModule->IsRuntimeMarshallingEnabled();
#ifdef FEATURE_COMINTEROP
runtimeMarshallingEnabled |= pMD && pMD->IsCLRToCOMCall();
#endif
for (ULONG i = 0; i < numArgs; i++)
{
SigPointer arg = ptr;
CorElementType type;
IfFailThrow(arg.PeekElemType(&type));
switch (type)
{
case ELEMENT_TYPE_PTR:
{
IfFailThrow(arg.GetElemType(NULL)); // skip ELEMENT_TYPE_PTR
IfFailThrow(arg.PeekElemType(&type));
if (runtimeMarshallingEnabled && type == ELEMENT_TYPE_VALUETYPE)
{
if ((arg.HasCustomModifier(pModule,
"Microsoft.VisualC.NeedsCopyConstructorModifier",
ELEMENT_TYPE_CMOD_REQD)) ||
(arg.HasCustomModifier(pModule,
"System.Runtime.CompilerServices.IsCopyConstructed",
ELEMENT_TYPE_CMOD_REQD)))
{
return TRUE;
}
}
if (i > 0) dwStackSize += TARGET_POINTER_SIZE;
break;
}
case ELEMENT_TYPE_INTERNAL:
// this check is not functional in DAC and provides no security against a malicious dump
// the DAC is prepared to receive an invalid type handle
#ifndef DACCESS_COMPILE
if (pModule->IsSigInIL(arg.GetPtr()))
THROW_BAD_FORMAT(BFA_BAD_SIGNATURE, (Module*)pModule);
#endif
FALLTHROUGH;
case ELEMENT_TYPE_VALUETYPE:
case ELEMENT_TYPE_GENERICINST:
{
TypeHandle hndArgType = arg.GetTypeHandleThrowing(pModule, pTypeContext);
bool isValidGeneric = IsValidForGenericMarshalling(hndArgType.GetMethodTable(), false, runtimeMarshallingEnabled);
if(!hndArgType.IsValueType() || !isValidGeneric)
return true;
if (hndArgType.GetMethodTable()->IsInt128OrHasInt128Fields())
{
// Int128 cannot be marshalled by value at this time
return TRUE;
}
// When the runtime runtime marshalling system is disabled, we don't support
// any types that contain gc pointers, but all "unmanaged" types are treated as blittable
// as long as they aren't auto-layout and don't have any auto-layout fields.
if (!runtimeMarshallingEnabled &&
!hndArgType.IsEnum() &&
(hndArgType.GetMethodTable()->ContainsPointers()
|| hndArgType.GetMethodTable()->IsAutoLayoutOrHasAutoLayoutField()))
{
return TRUE;
}
else if (runtimeMarshallingEnabled && !hndArgType.IsBlittable() && !hndArgType.IsEnum())
{
// When the runtime runtime marshalling system is enabled, we do special handling
// for any types that aren't blittable or enums.
return TRUE;
}
if (i > 0)
{
const bool isValueType = true;
dwStackSize += StackElemSize(hndArgType.GetSize(), isValueType, hndArgType.IsFloatHfa());
}
break;
}
case ELEMENT_TYPE_BOOLEAN:
case ELEMENT_TYPE_CHAR:
{
// When runtime marshalling is enabled:
// Bool requires marshaling
// Char may require marshaling (MARSHAL_TYPE_ANSICHAR)
if (runtimeMarshallingEnabled)
{
return TRUE;
}
}
FALLTHROUGH;
default:
{
if (CorTypeInfo::IsPrimitiveType(type) || type == ELEMENT_TYPE_PTR || type == ELEMENT_TYPE_FNPTR)
{
if (i > 0)
{
const bool isValueType = false;
const bool isFloatHfa = false;
dwStackSize += StackElemSize(CorTypeInfo::Size(type), isValueType, isFloatHfa);
}
}
else
{
// other non-primitive type - requires marshaling
return TRUE;
}
}
}
// check for explicit MarshalAs
NativeTypeParamInfo paramInfo;
// We only check the MarshalAs info when the runtime marshalling system is enabled.
// We ignore MarshalAs when the system is disabled, so no reason to disqualify from inlining
// when it is present.
if (runtimeMarshallingEnabled && pParamTokenArray[i] != mdParamDefNil)
{
if (!ParseNativeTypeInfo(pParamTokenArray[i], pMDImport, ¶mInfo) ||
paramInfo.m_NativeType != NATIVE_TYPE_DEFAULT)
{
// Presence of MarshalAs does not necessitate marshaling (it could as well be the default
// for the type), but it's a good enough heuristic. We definitely don't want to duplicate
// the logic from code:MarshalInfo.MarshalInfo here.
return TRUE;
}
}
IfFailThrow(ptr.SkipExactlyOne());
}
if (!FitsInU2(dwStackSize))
return TRUE;
// do not set the stack size for varargs - the number is call site specific
if (pMD != NULL && !pMD->IsVarArg())
{
if (pMD->IsNDirect())
{
((NDirectMethodDesc *)pMD)->SetStackArgumentSize(static_cast<WORD>(dwStackSize), callConv);
}
#ifdef FEATURE_COMINTEROP
else if (pMD->IsCLRToCOMCall())
{
// calling convention is always stdcall
((CLRToCOMCallMethodDesc *)pMD)->SetStackArgumentSize(static_cast<WORD>(dwStackSize));
}
#endif // FEATURE_COMINTEROP
}
return FALSE;
}
// factorization of CreateNDirectStubWorker
static MarshalInfo::MarshalType DoMarshalReturnValue(MetaSig& msig,
mdParamDef* params,
CorNativeLinkType nlType,
CorNativeLinkFlags nlFlags,
UINT argidx, // this is used for reverse pinvoke hresult swapping
StubState* pss,
int argOffset,
DWORD dwStubFlags,
MethodDesc *pMD,
UINT& nativeStackOffset,
bool& fStubNeedsCOM,
int nativeArgIndex
DEBUG_ARG(LPCUTF8 pDebugName)
DEBUG_ARG(LPCUTF8 pDebugClassName)
)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(params));
PRECONDITION(CheckPointer(pss));
PRECONDITION(CheckPointer(pMD, NULL_OK));
}
CONTRACTL_END;
MarshalInfo::MarshalType marshalType = (MarshalInfo::MarshalType) 0xcccccccc;
MarshalInfo::MarshalScenario ms;
#ifdef FEATURE_COMINTEROP
if (SF_IsCOMStub(dwStubFlags))
{
ms = MarshalInfo::MARSHAL_SCENARIO_COMINTEROP;
}
else
#endif // FEATURE_COMINTEROP
{
ms = MarshalInfo::MARSHAL_SCENARIO_NDIRECT;
}
if (msig.GetReturnType() != ELEMENT_TYPE_VOID)
{
MarshalInfo returnInfo(msig.GetModule(),
msig.GetReturnProps(),
msig.GetSigTypeContext(),
params[0],
ms,
nlType,
nlFlags,
FALSE,
argidx,
msig.NumFixedArgs(),
SF_IsBestFit(dwStubFlags),
SF_IsThrowOnUnmappableChar(dwStubFlags),
TRUE,
pMD,
TRUE
DEBUG_ARG(pDebugName)
DEBUG_ARG(pDebugClassName)
DEBUG_ARG(0)
);
marshalType = returnInfo.GetMarshalType();
fStubNeedsCOM |= returnInfo.MarshalerRequiresCOM();
#ifdef FEATURE_COMINTEROP
if (SF_IsCOMStub(dwStubFlags))
{
// We don't support native methods that return VARIANTs, non-blittable structs, GUIDs, or DECIMALs directly.
if (marshalType == MarshalInfo::MARSHAL_TYPE_OBJECT ||
marshalType == MarshalInfo::MARSHAL_TYPE_VALUECLASS ||
marshalType == MarshalInfo::MARSHAL_TYPE_GUID ||
marshalType == MarshalInfo::MARSHAL_TYPE_DECIMAL)
{
if (!SF_IsHRESULTSwapping(dwStubFlags) && !SF_IsCOMLateBoundStub(dwStubFlags))
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_COM_UNSUPPORTED_SIG);
}
}
pss->MarshalReturn(&returnInfo, argOffset);
}
else
#endif // FEATURE_COMINTEROP
{
if (marshalType == MarshalInfo::MARSHAL_TYPE_BLITTABLEVALUECLASS
|| marshalType == MarshalInfo::MARSHAL_TYPE_VALUECLASS
|| marshalType == MarshalInfo::MARSHAL_TYPE_GUID
|| marshalType == MarshalInfo::MARSHAL_TYPE_DECIMAL
)
{
if (SF_IsHRESULTSwapping(dwStubFlags))
{
// V1 restriction: we could implement this but it's late in the game to do so.
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_UNSUPPORTED_SIG);
}
}
else if (marshalType == MarshalInfo::MARSHAL_TYPE_CURRENCY
|| marshalType == MarshalInfo::MARSHAL_TYPE_ARRAYWITHOFFSET
|| marshalType == MarshalInfo::MARSHAL_TYPE_ARGITERATOR
#ifdef FEATURE_COMINTEROP
|| marshalType == MarshalInfo::MARSHAL_TYPE_OLECOLOR
#endif // FEATURE_COMINTEROP
)
{
// Each of these types are non-blittable and according to its managed size should be returned in a return buffer on x86 in stdcall.
// However, its native size is small enough to be returned by-value.
// We don't know the native type representation early enough to get this correct, so we throw an exception here.
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_UNSUPPORTED_SIG);
}
else if (IsUnsupportedTypedrefReturn(msig))
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_UNSUPPORTED_SIG);
}
#ifdef FEATURE_COMINTEROP
if (marshalType == MarshalInfo::MARSHAL_TYPE_OBJECT && !SF_IsHRESULTSwapping(dwStubFlags))
{
// No support for returning variants. This is a V1 restriction, due to the late date,
// don't want to add the special-case code to support this in light of low demand.
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NOVARIANTRETURN);
}
#endif // FEATURE_COMINTEROP
pss->MarshalReturn(&returnInfo, argOffset);
}
}
return marshalType;
}
static inline UINT GetStackOffsetFromStackSize(UINT stackSize, bool fThisCall)
{
LIMITED_METHOD_CONTRACT;
#ifdef TARGET_X86
if (fThisCall)
{
// -1 means that the argument is not on the stack
return (stackSize >= TARGET_POINTER_SIZE ? (stackSize - TARGET_POINTER_SIZE) : (UINT)-1);
}
#endif // TARGET_X86
return stackSize;
}
//---------------------------------------------------------
// Creates a new stub for a N/Direct call. Return refcount is 1.
// Note that this function may now throw if it fails to create
// a stub.
//---------------------------------------------------------
static void CreateNDirectStubWorker(StubState* pss,
StubSigDesc* pSigDesc,
CorNativeLinkType nlType,
CorNativeLinkFlags nlFlags,
CorInfoCallConvExtension unmgdCallConv,
DWORD dwStubFlags,
MethodDesc *pMD,
mdParamDef* pParamTokenArray,
int iLCIDArg
)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pss));
PRECONDITION(CheckPointer(pSigDesc));
PRECONDITION(CheckPointer(pMD, NULL_OK));
PRECONDITION(!pMD || pMD->IsILStub() || (0 != pMD->GetMethodTable()->IsDelegate()) == SF_IsDelegateStub(dwStubFlags));
}
CONTRACTL_END;
SF_ConsistencyCheck(dwStubFlags);
#ifdef _DEBUG
if (g_pConfig->ShouldBreakOnInteropStubSetup(pSigDesc->m_pDebugName))
CONSISTENCY_CHECK_MSGF(false, ("BreakOnInteropStubSetup: '%s' ", pSigDesc->m_pDebugName));
#endif // _DEBUG
bool runtimeMarshallingEnabled = SF_IsCOMStub(dwStubFlags) || pSigDesc->m_pMetadataModule->IsRuntimeMarshallingEnabled();
if (SF_IsCOMStub(dwStubFlags))
{
_ASSERTE(0 == nlType);
_ASSERTE(0 == nlFlags);
_ASSERTE(CallConv::GetDefaultUnmanagedCallingConvention() == unmgdCallConv);
}
else
{
_ASSERTE(nlType == nltAnsi || nlType == nltUnicode);
}
Module *pModule = pSigDesc->m_pModule;
//
// Set up signature walking objects.
//
MetaSig msig(pSigDesc->m_sig,
pModule,
&pSigDesc->m_typeContext);
if (SF_IsVarArgStub(dwStubFlags))
{
if (!runtimeMarshallingEnabled)
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_DISABLEDMARSHAL_VARARGS);
}
msig.SetTreatAsVarArg();
}
bool fThisCall = (unmgdCallConv == CorInfoCallConvExtension::Thiscall);
if (nlFlags & nlfLastError)
{
if (!runtimeMarshallingEnabled)
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_DISABLEDMARSHAL_SETLASTERROR);
}
pss->SetLastError(TRUE);
}
// This has been in the product since forward P/Invoke via delegates was
// introduced. It's wrong, but please keep it for backward compatibility.
if (runtimeMarshallingEnabled && SF_IsDelegateStub(dwStubFlags))
pss->SetLastError(TRUE);
pss->BeginEmit(dwStubFlags);
if (-1 != iLCIDArg)
{
if (!runtimeMarshallingEnabled)
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_DISABLEDMARSHAL_LCID);
}
// The code to handle the LCID will call MarshalLCID before calling MarshalArgument
// on the argument the LCID should go after. So we just bump up the index here.
iLCIDArg++;
}
if (!runtimeMarshallingEnabled && SF_IsHRESULTSwapping(dwStubFlags))
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_DISABLEDMARSHAL_PRESERVESIG);
}
int numArgs = msig.NumFixedArgs();
// thiscall must have at least one parameter (the "this")
if (fThisCall && numArgs == 0)
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_BADNATL_THISCALL);
//
// Now, emit the IL.
//
int argOffset = 0;
MarshalInfo::MarshalType marshalType = (MarshalInfo::MarshalType) 0xcccccccc;
//
// Marshal the return value.
//
UINT nativeStackSize = (SF_IsCOMStub(dwStubFlags) ? TARGET_POINTER_SIZE : 0);
bool fStubNeedsCOM = SF_IsCOMStub(dwStubFlags);
//
// Marshal the arguments
//
MarshalInfo::MarshalScenario ms;
#ifdef FEATURE_COMINTEROP
if (SF_IsCOMStub(dwStubFlags))
{
ms = MarshalInfo::MARSHAL_SCENARIO_COMINTEROP;
}
else
#endif // FEATURE_COMINTEROP
{
ms = MarshalInfo::MARSHAL_SCENARIO_NDIRECT;
}
// Build up marshaling information for each of the method's parameters
SIZE_T cbParamMarshalInfo;
if (!ClrSafeInt<SIZE_T>::multiply(sizeof(MarshalInfo), numArgs, cbParamMarshalInfo))
{
COMPlusThrowHR(COR_E_OVERFLOW);
}
NewArrayHolder<BYTE> pbParamMarshalInfo(new BYTE[cbParamMarshalInfo]);
MarshalInfo *pParamMarshalInfo = reinterpret_cast<MarshalInfo *>(pbParamMarshalInfo.GetValue());
MetaSig paramInfoMSig(msig);
for (int i = 0; i < numArgs; ++i)
{
paramInfoMSig.NextArg();
new(&(pParamMarshalInfo[i])) MarshalInfo(paramInfoMSig.GetModule(),
paramInfoMSig.GetArgProps(),
paramInfoMSig.GetSigTypeContext(),
pParamTokenArray[i + 1],
ms,
nlType,
nlFlags,
TRUE,
i + 1,
numArgs,
SF_IsBestFit(dwStubFlags),
SF_IsThrowOnUnmappableChar(dwStubFlags),
TRUE,
pMD,
TRUE
DEBUG_ARG(pSigDesc->m_pDebugName)
DEBUG_ARG(pSigDesc->m_pDebugClassName)
DEBUG_ARG(i + 1));
}
// Marshal the parameters
int argidx = 1;
int nativeArgIndex = 0;
while (argidx <= numArgs)
{
//
// Check to see if this is the parameter after which we need to insert the LCID.
//
if (argidx == iLCIDArg)
{
pss->MarshalLCID(argidx);
nativeStackSize += TARGET_POINTER_SIZE;
if (SF_IsReverseStub(dwStubFlags))
argOffset++;
}
msig.NextArg();
MarshalInfo &info = pParamMarshalInfo[argidx - 1];
pss->MarshalArgument(&info, argOffset, GetStackOffsetFromStackSize(nativeStackSize, fThisCall));
nativeStackSize += info.GetNativeArgSize();
fStubNeedsCOM |= info.MarshalerRequiresCOM();
if (fThisCall && argidx == 1)
{
// make sure that the first parameter is enregisterable
if (info.GetNativeArgSize() > TARGET_POINTER_SIZE)
COMPlusThrow(kMarshalDirectiveException, IDS_EE_NDIRECT_BADNATL_THISCALL);
}
argidx++;
++nativeArgIndex;
}
// Check to see if this is the parameter after which we need to insert the LCID.
if (argidx == iLCIDArg)
{
pss->MarshalLCID(argidx);
nativeStackSize += TARGET_POINTER_SIZE;
if (SF_IsReverseStub(dwStubFlags))
argOffset++;
}
marshalType = DoMarshalReturnValue(msig,
pParamTokenArray,
nlType,
nlFlags,
argidx,
pss,
argOffset,
dwStubFlags,
pMD,
nativeStackSize,
fStubNeedsCOM,
nativeArgIndex
DEBUG_ARG(pSigDesc->m_pDebugName)
DEBUG_ARG(pSigDesc->m_pDebugClassName)
);
if (SF_IsHRESULTSwapping(dwStubFlags))
{
if (msig.GetReturnType() != ELEMENT_TYPE_VOID)
nativeStackSize += TARGET_POINTER_SIZE;
}
if (pMD->IsDynamicMethod())
{
// Set the native stack size to the IL stub MD. It is needed for alignment
// thunk generation on the Mac and stdcall name decoration on Windows.
// We do not store it directly in the interop MethodDesc here because due
// to sharing we come here only for the first call with given signature and
// the target MD may even be NULL.
#ifdef TARGET_X86
if (fThisCall)
{
_ASSERTE(nativeStackSize >= TARGET_POINTER_SIZE);
nativeStackSize -= TARGET_POINTER_SIZE;
}
#endif // TARGET_X86
nativeStackSize = ALIGN_UP(nativeStackSize, TARGET_POINTER_SIZE);
if (!FitsInU2(nativeStackSize))
COMPlusThrow(kMarshalDirectiveException, IDS_EE_SIGTOOCOMPLEX);
DynamicMethodDesc *pDMD = pMD->AsDynamicMethodDesc();
pDMD->SetNativeStackArgSize(static_cast<WORD>(nativeStackSize));
if (fStubNeedsCOM)
pDMD->SetFlags(DynamicMethodDesc::FlagRequiresCOM);
}
// FinishEmit needs to know the native stack arg size so we call it after the number
// has been set in the stub MD (code:DynamicMethodDesc.SetNativeStackArgSize)
pss->FinishEmit(pMD);
}
static void CreateStructStub(ILStubState* pss,
StubSigDesc* pSigDesc,
MethodTable* pMT,
DWORD dwStubFlags,
MethodDesc* pMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pss));
PRECONDITION(CheckPointer(pSigDesc));
PRECONDITION(CheckPointer(pMD, NULL_OK));
PRECONDITION(!pMD || pMD->IsILStub());
PRECONDITION(SF_IsStructMarshalStub(dwStubFlags));
}
CONTRACTL_END;
SF_ConsistencyCheck(dwStubFlags);
#ifdef _DEBUG
if (g_pConfig->ShouldBreakOnInteropStubSetup(pSigDesc->m_pDebugName))
CONSISTENCY_CHECK_MSGF(false, ("BreakOnInteropStubSetup: '%s' ", pSigDesc->m_pDebugName));
#endif // _DEBUG
Module* pModule = pSigDesc->m_pModule;
pss->SetLastError(false);
pss->BeginEmit(dwStubFlags);
// Marshal the fields
MarshalInfo::MarshalScenario ms = MarshalInfo::MARSHAL_SCENARIO_FIELD;
EEClassNativeLayoutInfo const* pNativeLayoutInfo = pMT->GetNativeLayoutInfo();
int numFields = pNativeLayoutInfo->GetNumFields();
CorNativeLinkType nlType = pMT->GetCharSet();
NativeFieldDescriptor const* pFieldDescriptors = pNativeLayoutInfo->GetNativeFieldDescriptors();
const bool isInlineArray = pMT->GetClass()->IsInlineArray();
if (isInlineArray)
{
_ASSERTE(pNativeLayoutInfo->GetSize() % pFieldDescriptors[0].NativeSize() == 0);
numFields = pNativeLayoutInfo->GetSize() / pFieldDescriptors[0].NativeSize();
}
for (int i = 0; i < numFields; ++i)
{
// For inline arrays, we only have one field descriptor that we need to reuse for each field.
NativeFieldDescriptor const& nativeFieldDescriptor = isInlineArray ? pFieldDescriptors[0] : pFieldDescriptors[i];
PTR_FieldDesc pFD = nativeFieldDescriptor.GetFieldDesc();
SigPointer fieldSig = pFD->GetSigPointer();
// The first byte in a field signature is always 0x6 per ECMA 335. Skip over this byte to get to the rest of the signature for the MarshalInfo constructor.
(void)fieldSig.GetByte(nullptr);
SigTypeContext context(pFD, TypeHandle(pMT));
MarshalInfo mlInfo(pFD->GetModule(),
fieldSig,
&context,
pFD->GetMemberDef(),
ms,
nlType,
nlfNone,
TRUE,
i + 1,
numFields,
SF_IsBestFit(dwStubFlags),
SF_IsThrowOnUnmappableChar(dwStubFlags),
TRUE,
pMD,
TRUE
DEBUG_ARG(pSigDesc->m_pDebugName)
DEBUG_ARG(pSigDesc->m_pDebugClassName)
DEBUG_ARG(-1 /* field */));
// When we have an inline array, we need to calculate the offset based on how many elements we've already seen.
// Otherwise, we have a specific field descriptor for the given field that contains the correct offset info.
UINT32 managedOffset = isInlineArray ? (i * pFD->GetSize()) : pFD->GetOffset();
UINT32 externalOffset = isInlineArray ? (i * nativeFieldDescriptor.NativeSize()) : nativeFieldDescriptor.GetExternalOffset();
pss->MarshalField(&mlInfo, managedOffset, externalOffset, pFD);
}
if (pMD->IsDynamicMethod())
{
DynamicMethodDesc* pDMD = pMD->AsDynamicMethodDesc();
pDMD->SetNativeStackArgSize(4 * TARGET_POINTER_SIZE); // The native stack arg size is constant since the signature for struct stubs is constant.
}
// FinishEmit needs to know the native stack arg size so we call it after the number
// has been set in the stub MD (code:DynamicMethodDesc.SetNativeStackArgSize)
pss->FinishEmit(pMD);
}
namespace
{
class NDirectStubParameters
{
public:
NDirectStubParameters(Signature sig,
SigTypeContext* pTypeContext,
Module* pModule,
Module* pLoaderModule,
CorNativeLinkType nlType,
CorNativeLinkFlags nlFlags,
CorInfoCallConvExtension unmgdCallConv,
DWORD dwStubFlags, // NDirectStubFlags
int nParamTokens,
mdParamDef* pParamTokenArray,
int iLCIDArg,
MethodTable* pMT
) :
m_sig(sig),
m_pTypeContext(pTypeContext),
m_pModule(pModule),
m_pLoaderModule(pLoaderModule),
m_pParamTokenArray(pParamTokenArray),
m_unmgdCallConv(unmgdCallConv),
m_nlType(nlType),
m_nlFlags(nlFlags),
m_dwStubFlags(dwStubFlags),
m_iLCIDArg(iLCIDArg),
m_nParamTokens(nParamTokens),
m_pMT(pMT)
{
LIMITED_METHOD_CONTRACT;
}
Signature m_sig;
SigTypeContext* m_pTypeContext;
Module* m_pModule;
Module* m_pLoaderModule;
mdParamDef* m_pParamTokenArray;
CorInfoCallConvExtension m_unmgdCallConv;
CorNativeLinkType m_nlType;
CorNativeLinkFlags m_nlFlags;
DWORD m_dwStubFlags;
int m_iLCIDArg;
int m_nParamTokens;
MethodTable* m_pMT;
};
class NDirectStubHashBlob : public ILStubHashBlobBase
{
public:
Module* m_pModule;
MethodTable* m_pMT;
WORD m_unmgdCallConv;
BYTE m_nlType; // C_ASSERTS are in NDirect::CreateHashBlob
BYTE m_nlFlags;
DWORD m_StubFlags;
INT32 m_iLCIDArg;
INT32 m_nParams;
BYTE m_rgbSigAndParamData[1];
// (dwParamAttr, cbNativeType) // length: number of parameters
// NativeTypeBlob // length: number of parameters
// BYTE m_rgbSigData[]; // length: determined by sig walk
};
// For better performance and less memory fragmentation,
// I'm using structure here to avoid allocating 3 different arrays.
struct ParamInfo
{
DWORD dwParamAttr;
ULONG cbNativeType;
PCCOR_SIGNATURE pvNativeType;
};
ILStubHashBlob* CreateHashBlob(NDirectStubParameters* pParams)
{
STANDARD_VM_CONTRACT;
NDirectStubHashBlob* pBlob;
IMDInternalImport* pInternalImport = pParams->m_pModule->GetMDImport();
CQuickBytes paramInfoBytes;
paramInfoBytes.AllocThrows(sizeof(ParamInfo)*pParams->m_nParamTokens);
ParamInfo *paramInfos = (ParamInfo *)paramInfoBytes.Ptr();
::ZeroMemory(paramInfos, sizeof(ParamInfo) * pParams->m_nParamTokens);
size_t cbNativeTypeTotal = 0;
//
// Collect information for function parameters
//
for (int idx = 0; idx < pParams->m_nParamTokens; idx++)
{
mdParamDef token = pParams->m_pParamTokenArray[idx];
if (TypeFromToken(token) == mdtParamDef && mdParamDefNil != token)
{
USHORT usSequence_Ignore; // We don't need usSequence in the hash as the param array is already sorted
LPCSTR szParamName_Ignore;
IfFailThrow(pInternalImport->GetParamDefProps(token, &usSequence_Ignore, ¶mInfos[idx].dwParamAttr, &szParamName_Ignore));
if (paramInfos[idx].dwParamAttr & pdHasFieldMarshal)
{
IfFailThrow(pInternalImport->GetFieldMarshal(token, ¶mInfos[idx].pvNativeType, ¶mInfos[idx].cbNativeType));
cbNativeTypeTotal += paramInfos[idx].cbNativeType;
}
}
}
SigPointer sigPtr = pParams->m_sig.CreateSigPointer();
// note that ConvertToInternalSignature also resolves generics so different instantiations will get different
// hash blobs for methods that have generic parameters in their signature
SigBuilder sigBuilder;
sigPtr.ConvertToInternalSignature(pParams->m_pModule, pParams->m_pTypeContext, &sigBuilder, /* bSkipCustomModifier = */ FALSE);
DWORD cbSig;
PVOID pSig = sigBuilder.GetSignature(&cbSig);
//
// Build hash blob for IL stub sharing
//
S_SIZE_T cbSizeOfBlob = S_SIZE_T(offsetof(NDirectStubHashBlob, m_rgbSigAndParamData)) +
S_SIZE_T(sizeof(ULONG)) * S_SIZE_T(pParams->m_nParamTokens) + // Parameter attributes
S_SIZE_T(sizeof(DWORD)) * S_SIZE_T(pParams->m_nParamTokens) + // Native type blob size
S_SIZE_T(cbNativeTypeTotal) + // Native type blob data
S_SIZE_T(cbSig); // Signature
if (cbSizeOfBlob.IsOverflow())
COMPlusThrowHR(COR_E_OVERFLOW);
static_assert_no_msg(nltMaxValue <= 0xFF);
static_assert_no_msg(nlfMaxValue <= 0xFF);
static_assert_no_msg(pmMaxValue <= 0xFFFF);
NewArrayHolder<BYTE> pBytes = new BYTE[cbSizeOfBlob.Value()];
// zero out the hash bytes to ensure all bit fields are deterministically set
ZeroMemory(pBytes, cbSizeOfBlob.Value());
pBlob = (NDirectStubHashBlob*)(BYTE*)pBytes;
pBlob->m_pModule = NULL;
pBlob->m_pMT = pParams->m_pMT;
pBlob->m_cbSizeOfBlob = cbSizeOfBlob.Value();
pBlob->m_unmgdCallConv = static_cast<WORD>(pParams->m_unmgdCallConv);
pBlob->m_nlType = static_cast<BYTE>(pParams->m_nlType);
pBlob->m_nlFlags = static_cast<BYTE>(pParams->m_nlFlags & ~nlfNoMangle); // this flag does not affect the stub
pBlob->m_iLCIDArg = pParams->m_iLCIDArg;
pBlob->m_StubFlags = pParams->m_dwStubFlags;
pBlob->m_nParams = pParams->m_nParamTokens;
BYTE* pBlobParams = &pBlob->m_rgbSigAndParamData[0];
//
// Write (dwParamAttr, cbNativeType) for parameters
//
// Note that these need to be aligned and it is why they are written before the byte blobs
// I'm putting asserts here so that it will assert even in non-IA64 platforms to catch bugs
//
_ASSERTE((DWORD_PTR)pBlobParams % sizeof(DWORD) == 0);
_ASSERTE(sizeof(DWORD) == sizeof(ULONG));
for (int i = 0; i < pParams->m_nParamTokens; ++i)
{
// We only care about In/Out/HasFieldMarshal
// Other attr are about optional/default values which are not used in marshalling,
// but only used in compilers
*((DWORD *)pBlobParams) = paramInfos[i].dwParamAttr & (pdIn | pdOut | pdHasFieldMarshal);
pBlobParams += sizeof(DWORD);
*((ULONG *)pBlobParams) = paramInfos[i].cbNativeType;
pBlobParams += sizeof(ULONG);
}
//
// Write native type blob for parameters
//
for (int i = 0; i < pParams->m_nParamTokens; ++i)
{
if (paramInfos[i].cbNativeType > 0)
memcpy(pBlobParams, paramInfos[i].pvNativeType, paramInfos[i].cbNativeType);
pBlobParams += paramInfos[i].cbNativeType;
}
//
// Copy signature
//
memcpy(pBlobParams, pSig, cbSig);
// Verify that we indeed have reached the end
_ASSERTE(pBlobParams + cbSig == (BYTE *)pBlob + cbSizeOfBlob.Value());
pBytes.SuppressRelease();
return (ILStubHashBlob*)pBlob;
}
ILStubCache* GetILStubCache(NDirectStubParameters* pParams)
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// Use the m_pLoaderModule instead of m_pModule
// They could be different for methods on generic types.
return pParams->m_pLoaderModule->GetILStubCache();
}
MethodDesc* GetStubMethodDesc(
MethodDesc *pTargetMD,
NDirectStubParameters* pParams,
ILStubHashBlob* pHashParams,
AllocMemTracker* pamTracker,
bool& bILStubCreator,
MethodDesc* pLastMD)
{
CONTRACT(MethodDesc*)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pParams));
PRECONDITION(!pParams->m_sig.IsEmpty());
PRECONDITION(CheckPointer(pParams->m_pModule));
PRECONDITION(CheckPointer(pTargetMD, NULL_OK));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
MethodDesc* pMD;
ILStubCache* pCache = GetILStubCache(pParams);
pMD = pCache->GetStubMethodDesc(pTargetMD,
pHashParams,
pParams->m_dwStubFlags,
pParams->m_pModule,
pParams->m_pLoaderModule,
pParams->m_sig.GetRawSig(),
pParams->m_sig.GetRawSigLen(),
pParams->m_pTypeContext,
pamTracker,
bILStubCreator,
pLastMD);
RETURN pMD;
}
void RemoveILStubCacheEntry(NDirectStubParameters* pParams, ILStubHashBlob* pHashParams)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pParams));
PRECONDITION(CheckPointer(pHashParams));
PRECONDITION(!pParams->m_sig.IsEmpty());
PRECONDITION(CheckPointer(pParams->m_pModule));
}
CONTRACTL_END;
LOG((LF_STUBS, LL_INFO1000, "Exception happened when generating IL of stub clr!CreateInteropILStub StubMD: %p, HashBlob: %p \n", pParams, pHashParams));
ILStubCache* pCache = GetILStubCache(pParams);
pCache->DeleteEntry(pHashParams);
}
void AddMethodDescChunkWithLockTaken(NDirectStubParameters* pParams, MethodDesc *pMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pParams));
PRECONDITION(!pParams->m_sig.IsEmpty());
PRECONDITION(CheckPointer(pParams->m_pModule));
}
CONTRACTL_END;
ILStubCache* pCache = GetILStubCache(pParams);
pCache->AddMethodDescChunkWithLockTaken(pMD);
}
}
//
// Additional factorization of CreateNDirectStub. This hoists all the metadata accesses
// into one location so that we can leave CreateNDirectStubWorker to just generate the
// IL. This allows us to cache a stub based on the inputs to CreateNDirectStubWorker
// instead of having to generate the IL first before doing the caching.
//
static void CreateNDirectStubAccessMetadata(
StubSigDesc* pSigDesc, // IN
CorInfoCallConvExtension unmgdCallConv, // IN
DWORD* pdwStubFlags, // IN/OUT
int* piLCIDArg, // OUT
int* pNumArgs // OUT
)
{
STANDARD_VM_CONTRACT;
if (SF_IsCOMStub(*pdwStubFlags))
{
_ASSERTE(CallConv::GetDefaultUnmanagedCallingConvention() == unmgdCallConv);
}
else
{
if (unmgdCallConv == CorInfoCallConvExtension::Managed ||
unmgdCallConv == CorInfoCallConvExtension::Fastcall ||
unmgdCallConv == CorInfoCallConvExtension::FastcallMemberFunction)
{
COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV);
}
}
MetaSig msig(pSigDesc->m_sig,
pSigDesc->m_pModule,
&pSigDesc->m_typeContext);
if (SF_IsVarArgStub(*pdwStubFlags))
msig.SetTreatAsVarArg();
(*pNumArgs) = msig.NumFixedArgs();
IMDInternalImport* pInternalImport = pSigDesc->m_pModule->GetMDImport();
_ASSERTE(!SF_IsHRESULTSwapping(*pdwStubFlags));
mdMethodDef md = pSigDesc->m_tkMethodDef;
if (md != mdMethodDefNil)
{
DWORD dwDescrOffset;
DWORD dwImplFlags;
IfFailThrow(pInternalImport->GetMethodImplProps(
md,
&dwDescrOffset,
&dwImplFlags));
if (SF_IsReverseStub(*pdwStubFlags))
{
// only COM-to-CLR call supports hresult swapping in the reverse direction
if (SF_IsCOMStub(*pdwStubFlags) && !IsMiPreserveSig(dwImplFlags))
{
(*pdwStubFlags) |= NDIRECTSTUB_FL_DOHRESULTSWAPPING;
}
}
else
{
// fwd pinvoke, fwd com interop support hresult swapping.
// delegate to an unmanaged method does not.
if (!IsMiPreserveSig(dwImplFlags) && !SF_IsDelegateStub(*pdwStubFlags))
{
(*pdwStubFlags) |= NDIRECTSTUB_FL_DOHRESULTSWAPPING;
}
}
}
int lcidArg = -1;
// Check if we have a MethodDesc to query for additional data.
if (pSigDesc->m_pMD != NULL)
{
MethodDesc* pMD = pSigDesc->m_pMD;
// P/Invoke marked with UnmanagedCallersOnlyAttribute is not
// presently supported.
if (pMD->HasUnmanagedCallersOnlyAttribute())
COMPlusThrow(kNotSupportedException, IDS_EE_NDIRECT_UNSUPPORTED_SIG);
// Check to see if we need to do LCID conversion.
lcidArg = GetLCIDParameterIndex(pMD);
if (lcidArg != -1 && lcidArg > (*pNumArgs))
COMPlusThrow(kIndexOutOfRangeException, IDS_EE_INVALIDLCIDPARAM);
}
(*piLCIDArg) = lcidArg;
if (SF_IsCOMStub(*pdwStubFlags))
{
CONSISTENCY_CHECK(msig.HasThis());
}
else
{
if (msig.HasThis() && !SF_IsDelegateStub(*pdwStubFlags))
{
COMPlusThrow(kInvalidProgramException, VLDTR_E_FMD_PINVOKENOTSTATIC);
}
}
}
namespace
{
void PopulateNDirectMethodDescImpl(
_Inout_ NDirectMethodDesc* pNMD,
_In_ const PInvokeStaticSigInfo& sigInfo,
_In_opt_z_ LPCUTF8 libName,
_In_opt_z_ LPCUTF8 entryPointName)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pNMD != NULL);
}
CONTRACTL_END;
WORD ndirectflags = 0;
if (pNMD->MethodDesc::IsVarArg())
ndirectflags |= NDirectMethodDesc::kVarArgs;
if (sigInfo.GetCharSet() == nltAnsi)
ndirectflags |= NDirectMethodDesc::kNativeAnsi;
CorNativeLinkFlags linkflags = sigInfo.GetLinkFlags();
if (linkflags & nlfLastError)
ndirectflags |= NDirectMethodDesc::kLastError;
if (linkflags & nlfNoMangle)
ndirectflags |= NDirectMethodDesc::kNativeNoMangle;
CorInfoCallConvExtension callConv = sigInfo.GetCallConv();
if (callConv == CorInfoCallConvExtension::Stdcall)
ndirectflags |= NDirectMethodDesc::kStdCall;
if (callConv == CorInfoCallConvExtension::Thiscall)
ndirectflags |= NDirectMethodDesc::kThisCall;
if (pNMD->GetLoaderModule()->IsSystem() && (strcmp(libName, "QCall") == 0))
{
ndirectflags |= NDirectMethodDesc::kIsQCall;
}
else
{
pNMD->ndirect.m_pszLibName = libName;
}
pNMD->ndirect.m_pszEntrypointName = entryPointName;
// Do not publish incomplete prestub flags or you will introduce a race condition.
pNMD->InterlockedSetNDirectFlags(ndirectflags | NDirectMethodDesc::kNDirectPopulated);
}
}
void NDirect::PopulateNDirectMethodDesc(_Inout_ NDirectMethodDesc* pNMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pNMD));
}
CONTRACTL_END;
if (pNMD->IsSynchronized())
COMPlusThrow(kTypeLoadException, IDS_EE_NOSYNCHRONIZED);
if (pNMD->IsPopulated())
return;
LPCUTF8 szLibName = NULL, szEntryPointName = NULL;
PInvokeStaticSigInfo sigInfo(pNMD, &szLibName, &szEntryPointName);
PopulateNDirectMethodDescImpl(pNMD, sigInfo, szLibName, szEntryPointName);
}
void NDirect::InitializeSigInfoAndPopulateNDirectMethodDesc(_Inout_ NDirectMethodDesc* pNMD, _Inout_ PInvokeStaticSigInfo* pSigInfo)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pNMD));
PRECONDITION(CheckPointer(pSigInfo));
}
CONTRACTL_END;
if (pNMD->IsSynchronized())
COMPlusThrow(kTypeLoadException, IDS_EE_NOSYNCHRONIZED);
LPCUTF8 szLibName = NULL, szEntryPointName = NULL;
new (pSigInfo) PInvokeStaticSigInfo(pNMD, &szLibName, &szEntryPointName);
if (pNMD->IsPopulated())
return;
PopulateNDirectMethodDescImpl(pNMD, *pSigInfo, szLibName, szEntryPointName);
}
#ifdef FEATURE_COMINTEROP
// Find the MethodDesc of the predefined IL stub method by either
// 1) looking at redirected adapter interfaces, OR
// 2) looking at special attributes for the specific interop scenario (specified by dwStubFlags).
// Currently only ManagedToNativeComInteropStubAttribute is supported.
// It returns NULL if no such attribute(s) can be found.
// But if the attribute is found and is invalid, or something went wrong in the looking up
// process, an exception will be thrown. If everything goes well, you'll get the MethodDesc
// of the stub method
HRESULT FindPredefinedILStubMethod(MethodDesc *pTargetMD, DWORD dwStubFlags, MethodDesc **ppRetStubMD)
{
CONTRACT(HRESULT)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(CheckPointer(pTargetMD));
PRECONDITION(CheckPointer(ppRetStubMD));
PRECONDITION(*ppRetStubMD == NULL);
}
CONTRACT_END;
HRESULT hr;
MethodTable *pTargetMT = pTargetMD->GetMethodTable();
//
// Find out if we have the attribute
//
const void *pBytes;
ULONG cbBytes;
// Support v-table forward classic COM interop calls only
if (SF_IsCOMStub(dwStubFlags) && SF_IsForwardStub(dwStubFlags))
{
if (pTargetMT->HasInstantiation())
{
// ManagedToNativeComInteropStubAttribute is not supported with generics
return E_FAIL;
}
if (pTargetMD->IsFCall())
{
// ManagedToNativeComInteropStubAttribute is not supported on FCalls (i.e. methods on legacy
// interfaces forwarded to CustomMarshalers.dll such as IEnumerable::GetEnumerator)
return E_FAIL;
}
_ASSERTE(pTargetMD->IsCLRToCOMCall());
if (pTargetMD->IsInterface())
{
hr = pTargetMD->GetCustomAttribute(
WellKnownAttribute::ManagedToNativeComInteropStub,
&pBytes,
&cbBytes);
if (FAILED(hr))
RETURN hr;
// GetCustomAttribute returns S_FALSE when it cannot find the attribute but nothing fails...
// Translate that to E_FAIL
else if (hr == S_FALSE)
RETURN E_FAIL;
}
else
{
// We are dealing with the class, use the interface MD instead
// After second thought I believe we don't need to check the class MD.
// We can think stubs as part of public interface, and if the interface is public,
// the stubs should also be accessible
MethodDesc *pInterfaceMD = pTargetMD->GetInterfaceMD();
if (pInterfaceMD)
{
hr = FindPredefinedILStubMethod(pInterfaceMD, dwStubFlags, ppRetStubMD);
RETURN hr;
}
else
RETURN E_FAIL;
}
}
else
RETURN E_FAIL;
//
// Parse the attribute
//
CustomAttributeParser parser(pBytes, cbBytes);
IfFailRet(parser.SkipProlog());
LPCUTF8 pTypeName;
ULONG cbTypeName;
IfFailRet(parser.GetNonEmptyString(&pTypeName, &cbTypeName));
LPCUTF8 pMethodName;
ULONG cbMethodName;
IfFailRet(parser.GetNonEmptyString(&pMethodName, &cbMethodName));
StackSString typeName(SString::Utf8, pTypeName, cbTypeName);
StackSString methodName(SString::Utf8, pMethodName, cbMethodName);
//
// Retrieve the type
//
TypeHandle stubClassType;
stubClassType = TypeName::GetTypeReferencedByCustomAttribute(typeName.GetUnicode(), pTargetMT->GetAssembly());
MethodTable *pStubClassMT = stubClassType.AsMethodTable();
StackSString stubClassName;
pStubClassMT->_GetFullyQualifiedNameForClassNestedAware(stubClassName);
StackSString targetInterfaceName;
pTargetMT->_GetFullyQualifiedNameForClassNestedAware(targetInterfaceName);
// Restrict to same assembly only to reduce test cost
if (stubClassType.GetAssembly() != pTargetMT->GetAssembly())
{
COMPlusThrow(
kArgumentException,
IDS_EE_INTEROP_STUB_CA_MUST_BE_WITHIN_SAME_ASSEMBLY,
stubClassName.GetUnicode(),
targetInterfaceName.GetUnicode()
);
}
if (stubClassType.HasInstantiation())
{
COMPlusThrow(
kArgumentException,
IDS_EE_INTEROP_STUB_CA_STUB_CLASS_MUST_NOT_BE_GENERIC,
stubClassName.GetUnicode()
);
}
if (stubClassType.IsInterface())
{
COMPlusThrow(
kArgumentException,
IDS_EE_INTEROP_STUB_CA_STUB_CLASS_MUST_NOT_BE_INTERFACE,
stubClassName.GetUnicode()
);
}
//
// Locate the MethodDesc for the stub method
//
MethodDesc *pStubMD = NULL;
{
PCCOR_SIGNATURE pTargetSig = NULL;
DWORD pcTargetSig = 0;
SigTypeContext typeContext; // NO generics supported
pTargetMD->GetSig(&pTargetSig, &pcTargetSig);
MetaSig msig(pTargetSig,
pcTargetSig,
pTargetMD->GetModule(),
&typeContext);
_ASSERTE(msig.HasThis());
SigBuilder stubSigBuilder;
//
// Append calling Convention, NumOfArgs + 1,
//
stubSigBuilder.AppendByte(msig.GetCallingConvention() & ~IMAGE_CEE_CS_CALLCONV_HASTHIS);
stubSigBuilder.AppendData(msig.NumFixedArgs() + 1);
//
// Append return type
//
SigPointer pReturn = msig.GetReturnProps();
LPBYTE pReturnTypeBegin = (LPBYTE)pReturn.GetPtr();
IfFailThrow(pReturn.SkipExactlyOne());
LPBYTE pReturnTypeEnd = (LPBYTE)pReturn.GetPtr();
stubSigBuilder.AppendBlob(pReturnTypeBegin, pReturnTypeEnd - pReturnTypeBegin);
//
// Append 'this'
//
stubSigBuilder.AppendElementType(ELEMENT_TYPE_CLASS);
stubSigBuilder.AppendToken(pTargetMT->GetCl());
//
// Copy rest of the arguments
//
if (msig.NextArg() != ELEMENT_TYPE_END)
{
SigPointer pFirstArg = msig.GetArgProps();
LPBYTE pArgBegin = (LPBYTE) pFirstArg.GetPtr();
LPBYTE pArgEnd = (LPBYTE) pTargetSig + pcTargetSig;
stubSigBuilder.AppendBlob(pArgBegin, pArgEnd - pArgBegin);
}
//
// Allocate new memory and copy over
//
DWORD pcStubSig = 0;
PCCOR_SIGNATURE pStubSig = (PCCOR_SIGNATURE) stubSigBuilder.GetSignature(&pcStubSig);
//
// Find method using name + signature
//
StackSString buffer;
buffer.SetAndConvertToUTF8(methodName);
LPCUTF8 szMethodNameUTF8 = buffer.GetUTF8();
pStubMD = MemberLoader::FindMethod(stubClassType.GetMethodTable(),
szMethodNameUTF8,
pStubSig,
pcStubSig,
pTargetMT->GetModule());
if (pStubMD == NULL)
{
CQuickBytes qbSig;
PrettyPrintSig(
pStubSig,
pcStubSig,
szMethodNameUTF8,
&qbSig,
pTargetMD->GetMDImport(),
NULL);
// Unfortunately the PrettyPrintSig doesn't print 'static' when the function is static
// so we need to append 'static' here. No need to localize
SString signature(SString::Utf8, (LPCUTF8)"static ");
signature.AppendUTF8((LPCUTF8) qbSig.Ptr());
COMPlusThrow(
kMissingMethodException,
IDS_EE_INTEROP_STUB_CA_STUB_METHOD_MISSING,
signature.GetUnicode(),
stubClassName.GetUnicode()
);
}
}
//
// Check the Stub MD
//
// Verify that the target interop method can call the stub method
_ASSERTE(pTargetMD != NULL);
AccessCheckContext accessContext(pTargetMD, pTargetMT);
if (!ClassLoader::CanAccess(
&accessContext,
pStubClassMT,
stubClassType.GetAssembly(),
pStubMD->GetAttrs(),
pStubMD,
NULL))
{
StackSString interopMethodName(SString::Utf8, pTargetMD->GetName());
COMPlusThrow(
kMethodAccessException,
IDS_EE_INTEROP_STUB_CA_NO_ACCESS_TO_STUB_METHOD,
interopMethodName.GetUnicode(),
methodName.GetUnicode()
);
}
// The FindMethod call will make sure that it is static by matching signature.
// So there is no need to check and throw
_ASSERTE(pStubMD->IsStatic());
*ppRetStubMD = pStubMD;
RETURN S_OK;
}
#endif // FEATURE_COMINTEROP
namespace
{
//=======================================================================
// ILStubCreatorHelper
// The class is used as a helper class in CreateInteropILStub. It mainly
// puts two methods NDirect::GetStubMethodDesc and NDirect::RemoveILStubCacheEntry
// into a holder. See CreateInteropILStub for more information
//=======================================================================
class ILStubCreatorHelper
{
public:
ILStubCreatorHelper(MethodDesc *pTargetMD,
NDirectStubParameters* pParams
) :
m_pTargetMD(pTargetMD),
m_pParams(pParams),
m_pStubMD(NULL),
m_bILStubCreator(false)
{
STANDARD_VM_CONTRACT;
m_pHashParams = CreateHashBlob(m_pParams);
}
~ILStubCreatorHelper()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
}
CONTRACTL_END;
RemoveILStubCacheEntry();
}
inline bool CreatedTheAssociatedPublishedStubMD()
{
return m_bILStubCreator;
}
inline void GetStubMethodDesc()
{
WRAPPER_NO_CONTRACT;
// The creator flag represents ownership of the associated stub MD and indicates that the
// stub MD has not been removed from the cache, so the lookup below is guaranteed to return
// this owned published stub MD.
#ifdef _DEBUG
MethodDesc* pPreexistingStubMD = m_pStubMD;
bool createdThePreexistingMD = m_bILStubCreator;
#endif // _DEBUG
m_pStubMD = ::GetStubMethodDesc(m_pTargetMD, m_pParams, m_pHashParams, &m_amTracker, m_bILStubCreator, m_pStubMD);
_ASSERTE(!createdThePreexistingMD || (m_bILStubCreator && (m_pStubMD == pPreexistingStubMD)));
}
inline void RemoveILStubCacheEntry()
{
WRAPPER_NO_CONTRACT;
if (m_bILStubCreator)
{
::RemoveILStubCacheEntry(m_pParams, m_pHashParams);
m_bILStubCreator = false;
}
}
inline MethodDesc* GetStubMD()
{
LIMITED_METHOD_CONTRACT;
return m_pStubMD;
}
inline void SuppressRelease()
{
WRAPPER_NO_CONTRACT;
m_bILStubCreator = false;
m_amTracker.SuppressRelease();
}
private:
MethodDesc* m_pTargetMD;
NDirectStubParameters* m_pParams;
NewArrayHolder<ILStubHashBlob> m_pHashParams;
AllocMemTracker* m_pAmTracker;
MethodDesc* m_pStubMD;
AllocMemTracker m_amTracker;
bool m_bILStubCreator; // Only the creator can remove the ILStub from the Cache
}; //ILStubCreatorHelper
MethodDesc* CreateInteropILStub(
ILStubState* pss,
StubSigDesc* pSigDesc,
CorNativeLinkType nlType,
CorNativeLinkFlags nlFlags,
CorInfoCallConvExtension unmgdCallConv,
int nParamTokens,
mdParamDef* pParamTokenArray,
int iLCIDArg,
bool* pGeneratedNewStub = nullptr
)
{
CONTRACT(MethodDesc*)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pSigDesc));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
///////////////////////////////
//
// MethodDesc creation
//
///////////////////////////////
MethodDesc* pStubMD = NULL;
Module* pModule = pSigDesc->m_pModule;
Module* pLoaderModule = pSigDesc->m_pLoaderModule;
MethodDesc* pTargetMD = pSigDesc->m_pMD;
MethodTable* pTargetMT = pSigDesc->m_pMT;
//
// pTargetMD may be null in the case of calli pinvoke
// and vararg pinvoke.
//
DWORD dwStubFlags = pss->GetFlags();
#ifdef FEATURE_COMINTEROP
//
// Try to locate predefined IL stub either defined in user code or hardcoded in CLR
// If there is one, use the pointed method as the stub.
// Skip pTargetMD == NULL case for reverse interop calls
//
if (pTargetMD && SUCCEEDED(FindPredefinedILStubMethod(pTargetMD, dwStubFlags, &pStubMD)))
{
// We are about to execute method in pStubMD which could be in another module.
// Call EnsureActive before make the call
// This cannot be done during NGEN/PEVerify (in PASSIVE_DOMAIN) so I've moved it here
pStubMD->EnsureActive();
RETURN pStubMD;
}
#endif // FEATURE_COMINTEROP
// Otherwise, fall back to generating IL stub on-the-fly
NDirectStubParameters params(pSigDesc->m_sig,
&pSigDesc->m_typeContext,
pModule,
pLoaderModule,
nlType,
nlFlags,
unmgdCallConv,
dwStubFlags,
nParamTokens,
pParamTokenArray,
iLCIDArg,
pSigDesc->m_pMT
);
// The following ILStubCreatorHelper is to recover the status when an
// exception happens during the generation of the IL stubs. We need to free the
// memory allocated and restore the ILStubCache.
//
// The following block is logically divided into two phases. The first phase is
// CreateOrGet IL Stub phase which we take a domain level lock. The second phase
// is IL generation phase which we take a MethodDesc level lock. Taking two locks
// is mainly designed for performance.
//
// ilStubCreatorHelper contains an instance of AllocMemTracker which tracks the
// allocated memory during the creation of MethodDesc so that we are able to remove
// them when destructing ILStubCreatorHelper
// When removing IL Stub from Cache, we have a constraint that only the thread which
// creates the stub can remove it. Otherwise, any thread hits cache and gets the stub will
// remove it from cache if OOM occurs
{
ILStubCreatorHelper ilStubCreatorHelper(pTargetMD, ¶ms);
// take the domain level lock
ListLockHolder pILStubLock(AppDomain::GetCurrentDomain()->GetILStubGenLock());
{
ilStubCreatorHelper.GetStubMethodDesc();
pStubMD = ilStubCreatorHelper.GetStubMD();
///////////////////////////////
//
// IL generation
//
///////////////////////////////
{
// take the MethodDesc level locker
ListLockEntryHolder pEntry(ListLockEntry::Find(pILStubLock, pStubMD, "il stub gen lock"));
ListLockEntryLockHolder pEntryLock(pEntry, FALSE);
// We have the entry lock we need to use, so we can release the global lock.
pILStubLock.Release();
{
ilStubCreatorHelper.GetStubMethodDesc();
if (!pEntryLock.DeadlockAwareAcquire())
{
// the IL generation is not recursive.
// However, we can encounter a recursive situation when attempting to
// marshal a struct containing a layout class containing another struct.
// Throw an exception here instead of asserting.
if (SF_IsStructMarshalStub(dwStubFlags))
{
_ASSERTE(pSigDesc->m_pMT != nullptr);
StackSString strTypeName;
TypeString::AppendType(strTypeName, TypeHandle(pSigDesc->m_pMT));
COMPlusThrow(kTypeLoadException, IDS_CANNOT_MARSHAL_RECURSIVE_DEF, strTypeName.GetUnicode());
}
UNREACHABLE_MSG("unexpected deadlock in IL stub generation!");
}
if (SF_IsSharedStub(params.m_dwStubFlags))
{
// We need to re-acquire the lock in case we need to get a new pStubMD
// in the case that the owner of the shared stub was destroyed.
pILStubLock.Acquire();
// Assure that pStubMD we have now has not been destroyed by other threads
ilStubCreatorHelper.GetStubMethodDesc();
while (pStubMD != ilStubCreatorHelper.GetStubMD())
{
pStubMD = ilStubCreatorHelper.GetStubMD();
pEntry.Assign(ListLockEntry::Find(pILStubLock, pStubMD, "il stub gen lock"));
pEntryLock.Assign(pEntry, FALSE);
// We have the entry lock we need to use, so we can release the global lock.
pILStubLock.Release();
if (!pEntryLock.DeadlockAwareAcquire())
{
// the IL generation is not recursive.
// However, we can encounter a recursive situation when attempting to
// marshal a struct containing a layout class containing another struct.
// Throw an exception here instead of asserting.
if (SF_IsStructMarshalStub(dwStubFlags))
{
_ASSERTE(pSigDesc->m_pMT != nullptr);
StackSString strTypeName;
TypeString::AppendType(strTypeName, TypeHandle(pSigDesc->m_pMT));
COMPlusThrow(kTypeLoadException, IDS_CANNOT_MARSHAL_RECURSIVE_DEF, strTypeName.GetUnicode());
}
UNREACHABLE_MSG("unexpected deadlock in IL stub generation!");
}
pILStubLock.Acquire();
ilStubCreatorHelper.GetStubMethodDesc();
}
}
for (;;)
{
// We have the entry lock now, we can release the global lock
pILStubLock.Release();
_ASSERTE(pEntryLock.GetValue()->HasLock());
if (pEntry->m_hrResultCode != S_FALSE)
{
// We came in to generate the IL but someone
// beat us so there's nothing to do
break;
}
ILStubResolver* pResolver = pStubMD->AsDynamicMethodDesc()->GetILStubResolver();
CONSISTENCY_CHECK((NULL == pResolver->GetStubMethodDesc()) || (pStubMD == pResolver->GetStubMethodDesc()));
if (pResolver->IsILGenerated())
{
// this stub already has its IL generated
break;
}
//
// Check that the stub signature and MethodDesc are compatible. The JIT
// interface functions depend on this.
//
{
SigPointer ptr = pSigDesc->m_sig.CreateSigPointer();
uint32_t callConvInfo;
IfFailThrow(ptr.GetCallingConvInfo(&callConvInfo));
BOOL fSigIsStatic = !(callConvInfo & IMAGE_CEE_CS_CALLCONV_HASTHIS);
// CreateNDirectStubWorker will throw an exception for these cases.
BOOL fCanHaveThis = SF_IsDelegateStub(dwStubFlags) || SF_IsCOMStub(dwStubFlags);
if (fSigIsStatic || fCanHaveThis)
{
CONSISTENCY_CHECK(pStubMD->IsStatic() == (DWORD)fSigIsStatic);
}
}
{
ILStubGenHolder sgh(pResolver);
pResolver->SetStubMethodDesc(pStubMD);
pResolver->SetStubTargetMethodDesc(pTargetMD);
if (SF_IsStructMarshalStub(dwStubFlags))
{
CreateStructStub(pss, pSigDesc, pTargetMT, dwStubFlags, pStubMD);
}
else
{
if (!pSigDesc->m_typeContext.IsEmpty())
{
// For generic calli, we only support blittable types
if (SF_IsCALLIStub(dwStubFlags)
&& NDirect::MarshalingRequired(NULL, pStubMD->GetSig(), pSigDesc->m_pModule, &pSigDesc->m_typeContext))
{
COMPlusThrow(kMarshalDirectiveException, IDS_EE_BADMARSHAL_GENERICS_RESTRICTION);
}
// We don't want to support generic varargs, so block it
else if (SF_IsVarArgStub(dwStubFlags))
{
COMPlusThrow(kNotSupportedException, BFA_GENCODE_NOT_BE_VARARG);
}
}
CreateNDirectStubWorker(pss,
pSigDesc,
nlType,
nlFlags,
unmgdCallConv,
dwStubFlags,
pStubMD,
pParamTokenArray,
iLCIDArg);
}
pResolver->SetTokenLookupMap(pss->GetTokenLookupMap());
pResolver->SetStubTargetMethodSig(
pss->GetStubTargetMethodSig(),
pss->GetStubTargetMethodSigLength());
// we successfully generated the IL stub
sgh.SuppressRelease();
}
pEntry->m_hrResultCode = S_OK;
break;
}
// Link the MethodDesc onto the method table with the lock taken
AddMethodDescChunkWithLockTaken(¶ms, pStubMD);
}
}
}
// Callers use the new stub indicator to distinguish between 1) the case where a new stub
// MD was generated during this call and 2) the case where this function attached to a stub
// MD that was generated by some other call (either a call that completed earlier or a call
// on a racing thread). In particular, reliably detecting case (1) is crucial because it is
// the only case where this call permanently publishes a new stub MD into the cache,
// meaning it is the only case where the caller cannot safely free any allocations (such as
// a signature buffer) which the stub MD might reference.
//
// Set the indicator if and only if the stub MD that will be imminiently returned to the
// caller was created by the code above (and will therefore become a permanent member of
// the cache when the SuppressRelease occurs below). Note that, in the presence of racing
// threads, the current call may or may not have carried out IL generation for the stub;
// the only important thing is whether the current call was the one that created the stub
// MD earlier on.
if (ilStubCreatorHelper.CreatedTheAssociatedPublishedStubMD())
{
if (pGeneratedNewStub)
{
*pGeneratedNewStub = true;
}
}
ilStubCreatorHelper.SuppressRelease();
}
#if defined(TARGET_X86)
if (SF_IsForwardStub(dwStubFlags) && pTargetMD != NULL && !pTargetMD->IsVarArg())
{
// copy the stack arg byte count from the stub MD to the target MD - this number is computed
// during stub generation and is copied to all target MDs that share the stub
// (we don't set it for varargs - the number is call site specific)
// also copy the "takes parameters with copy constructors" flag which is needed to generate
// appropriate intercept stub
WORD cbStackArgSize = pStubMD->AsDynamicMethodDesc()->GetNativeStackArgSize();
if (pTargetMD->IsNDirect())
{
NDirectMethodDesc *pTargetNMD = (NDirectMethodDesc *)pTargetMD;
pTargetNMD->SetStackArgumentSize(cbStackArgSize, CallConv::GetDefaultUnmanagedCallingConvention());
}
#ifdef FEATURE_COMINTEROP
else
{
if (SF_IsCOMStub(dwStubFlags))
{
CLRToCOMCallInfo *pComInfo = CLRToCOMCallInfo::FromMethodDesc(pTargetMD);
if (pComInfo != NULL)
{
pComInfo->SetStackArgumentSize(cbStackArgSize);
}
}
}
#endif // FEATURE_COMINTEROP
}
#endif // defined(TARGET_X86)
RETURN pStubMD;
}
}
MethodDesc* NDirect::CreateCLRToNativeILStub(
StubSigDesc* pSigDesc,
CorNativeLinkType nlType,
CorNativeLinkFlags nlFlags,
CorInfoCallConvExtension unmgdCallConv,
DWORD dwStubFlags) // NDirectStubFlags
{
CONTRACT(MethodDesc*)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pSigDesc));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
int iLCIDArg = 0;
int numArgs = 0;
int numParamTokens = 0;
mdParamDef* pParamTokenArray = NULL;
CreateNDirectStubAccessMetadata(pSigDesc,
unmgdCallConv,
&dwStubFlags,
&iLCIDArg,
&numArgs);
Module *pModule = pSigDesc->m_pModule;
numParamTokens = numArgs + 1;
pParamTokenArray = (mdParamDef*)_alloca(numParamTokens * sizeof(mdParamDef));
CollateParamTokens(pModule->GetMDImport(), pSigDesc->m_tkMethodDef, numArgs, pParamTokenArray);
MethodDesc *pMD = pSigDesc->m_pMD;
NewHolder<ILStubState> pStubState;
#ifdef FEATURE_COMINTEROP
if (SF_IsCOMStub(dwStubFlags))
{
if (SF_IsReverseStub(dwStubFlags))
{
pStubState = new COMToCLR_ILStubState(pModule, pSigDesc->m_sig, &pSigDesc->m_typeContext, dwStubFlags, iLCIDArg, pMD);
}
else
{
pStubState = new CLRToCOM_ILStubState(pModule, pSigDesc->m_sig, &pSigDesc->m_typeContext, dwStubFlags, iLCIDArg, pMD);
}
}
else
#endif
{
pStubState = new PInvoke_ILStubState(pModule, pSigDesc->m_sig, &pSigDesc->m_typeContext, dwStubFlags, unmgdCallConv, iLCIDArg, pMD);
}
MethodDesc* pStubMD;
pStubMD = CreateInteropILStub(
pStubState,
pSigDesc,
nlType,
nlFlags,
unmgdCallConv,
numParamTokens,
pParamTokenArray,
iLCIDArg);
RETURN pStubMD;
}
#ifdef FEATURE_COMINTEROP
MethodDesc* NDirect::CreateFieldAccessILStub(
PCCOR_SIGNATURE szMetaSig,
DWORD cbMetaSigSize,
Module* pModule,
mdFieldDef fd,
DWORD dwStubFlags, // NDirectStubFlags
FieldDesc* pFD)
{
CONTRACT(MethodDesc*)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(szMetaSig));
PRECONDITION(CheckPointer(pModule));
PRECONDITION(CheckPointer(pFD, NULL_OK));
PRECONDITION(SF_IsFieldGetterStub(dwStubFlags) || SF_IsFieldSetterStub(dwStubFlags));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
int numArgs = (SF_IsFieldSetterStub(dwStubFlags) ? 1 : 0);
int numParamTokens = numArgs + 1;
// make sure we capture marshaling metadata
mdParamDef* pParamTokenArray = (mdParamDef *)_alloca(numParamTokens * sizeof(mdParamDef));
pParamTokenArray[0] = mdParamDefNil;
pParamTokenArray[numArgs] = (mdParamDef)fd;
// fields are never preserve-sig
dwStubFlags |= NDIRECTSTUB_FL_DOHRESULTSWAPPING;
// convert field signature to getter/setter signature
SigBuilder sigBuilder;
sigBuilder.AppendData(IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS);
sigBuilder.AppendData(numArgs);
if (SF_IsFieldSetterStub(dwStubFlags))
{
// managed setter returns void
sigBuilder.AppendElementType(ELEMENT_TYPE_VOID);
}
CONSISTENCY_CHECK(*szMetaSig == IMAGE_CEE_CS_CALLCONV_FIELD);
sigBuilder.AppendBlob((const PVOID)(szMetaSig + 1), cbMetaSigSize - 1);
szMetaSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature(&cbMetaSigSize);
StubSigDesc sigDesc(Signature(szMetaSig, cbMetaSigSize), pModule);
#ifdef _DEBUG
sigDesc.m_pDebugName = pFD->GetDebugName();
sigDesc.m_pDebugClassName = pFD->GetEnclosingMethodTable()->GetDebugClassName();
#endif // _DEBUG
Signature signature(szMetaSig, cbMetaSigSize);
NewHolder<ILStubState> pStubState = new COMToCLRFieldAccess_ILStubState(pModule, signature, &sigDesc.m_typeContext, dwStubFlags, pFD);
MethodDesc* pStubMD;
pStubMD = CreateInteropILStub(
pStubState,
&sigDesc,
(CorNativeLinkType)0,
(CorNativeLinkFlags)0,
CallConv::GetDefaultUnmanagedCallingConvention(),
numParamTokens,
pParamTokenArray,
-1);
RETURN pStubMD;
}
#endif // FEATURE_COMINTEROP
#ifndef DACCESS_COMPILE
MethodDesc* NDirect::CreateStructMarshalILStub(MethodTable* pMT)
{
CONTRACT(MethodDesc*)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMT));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;
LoaderAllocator* pLoaderAllocator = pMT->GetLoaderAllocator();
EEMarshalingData* pMarshallingData = pLoaderAllocator->GetMarshalingData();
MethodDesc* pCachedStubMD = pMarshallingData->LookupStructILStub(pMT);
if (pCachedStubMD != NULL)
RETURN pCachedStubMD;
DWORD dwStubFlags = NDIRECTSTUB_FL_STRUCT_MARSHAL;
BOOL bestFit, throwOnUnmappableChar;
ReadBestFitCustomAttribute(pMT->GetModule(), pMT->GetCl(), &bestFit, &throwOnUnmappableChar);
if (bestFit == TRUE)
{
dwStubFlags |= NDIRECTSTUB_FL_BESTFIT;
}
if (throwOnUnmappableChar == TRUE)
{
dwStubFlags |= NDIRECTSTUB_FL_THROWONUNMAPPABLECHAR;
}
// ValueClass signature:
// void (ref Struct managedData, native Struct* nativeData, int marshalAction, ref CleanupWorkListElement cwl)
// LayoutClass signature:
// void (ref byte managedData, byte* nativeData, int marshalAction, ref CleanupWorkListElement cwl)
constexpr int numParamTokens = 1;
mdParamDef pParamTokenArray[numParamTokens];
pParamTokenArray[0] = (mdParamDef)pMT->GetCl();
FunctionSigBuilder sigBuilder;
sigBuilder.SetCallingConv(IMAGE_CEE_CS_CALLCONV_DEFAULT);
LocalDesc returnType(ELEMENT_TYPE_VOID);
sigBuilder.SetReturnType(&returnType);
if (pMT->IsValueType())
{
LocalDesc managedParameter(pMT);
managedParameter.MakeByRef();
sigBuilder.NewArg(&managedParameter);
LocalDesc nativeValueType(TypeHandle{ pMT }.MakeNativeValueType());
nativeValueType.MakePointer();
sigBuilder.NewArg(&nativeValueType);
}
else
{
LocalDesc byteRef(ELEMENT_TYPE_I1);
byteRef.MakeByRef();
sigBuilder.NewArg(&byteRef);
LocalDesc bytePtr(ELEMENT_TYPE_I1);
bytePtr.MakePointer();
sigBuilder.NewArg(&bytePtr);
}
LocalDesc i4(ELEMENT_TYPE_I4);
sigBuilder.NewArg(&i4);
LocalDesc cleanupWorkList(CoreLibBinder::GetClass(CLASS__CLEANUP_WORK_LIST_ELEMENT));
cleanupWorkList.MakeByRef();
sigBuilder.NewArg(&cleanupWorkList);
DWORD cbMetaSigSize = sigBuilder.GetSigSize();
AllocMemHolder<BYTE> szMetaSig(pLoaderAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(cbMetaSigSize)));
sigBuilder.GetSig(szMetaSig, cbMetaSigSize);
StubSigDesc sigDesc(pMT, Signature(szMetaSig, cbMetaSigSize), pMT->GetModule());
#ifdef _DEBUG
sigDesc.m_pDebugName = "Struct Marshalling Stub";
sigDesc.m_pDebugClassName = pMT->GetDebugClassName();
#endif // _DEBUG
Signature signature(szMetaSig, cbMetaSigSize);
NewHolder<ILStubState> pStubState = new StructMarshal_ILStubState(pMT, signature, &sigDesc.m_typeContext, dwStubFlags);
bool generatedNewStub = false;
MethodDesc* pStubMD;
pStubMD = CreateInteropILStub(
pStubState,
&sigDesc,
(CorNativeLinkType)0,
(CorNativeLinkFlags)0,
CorInfoCallConvExtension::Managed,
numParamTokens,
pParamTokenArray,
-1,
&generatedNewStub);
if (generatedNewStub) // If we generated a new stub, we need to keep the signature we created allocated.
{
szMetaSig.SuppressRelease();
}
// The CreateInteropILStub() handles only creating a single stub.
// The stub returned will be okay to return even if the call below loses
// the race to insert into the cache.
pMarshallingData->CacheStructILStub(pMT, pStubMD);
RETURN pStubMD;
}
PCODE NDirect::GetEntryPointForStructMarshalStub(MethodTable* pMT)
{
LIMITED_METHOD_CONTRACT;
MethodDesc* pMD = CreateStructMarshalILStub(pMT);
_ASSERTE(pMD != nullptr);
return pMD->GetMultiCallableAddrOfCode();
}
#endif // DACCESS_COMPILE
MethodDesc* NDirect::CreateCLRToNativeILStub(PInvokeStaticSigInfo* pSigInfo,
DWORD dwStubFlags,
MethodDesc* pMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pSigInfo != NULL);
}
CONTRACTL_END;
StubSigDesc sigDesc(pMD, pSigInfo->GetSignature(), pSigInfo->GetModule());
return CreateCLRToNativeILStub(&sigDesc,
pSigInfo->GetCharSet(),
pSigInfo->GetLinkFlags(),
pSigInfo->GetCallConv(),
pSigInfo->GetStubFlags() | dwStubFlags);
}
MethodDesc* NDirect::GetILStubMethodDesc(NDirectMethodDesc* pNMD, PInvokeStaticSigInfo* pSigInfo, DWORD dwStubFlags)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(pNMD != NULL);
}
CONTRACTL_END;
MethodDesc* pStubMD = NULL;
if (!pNMD->IsVarArgs() || SF_IsForNumParamBytes(dwStubFlags))
{
pStubMD = CreateCLRToNativeILStub(
pSigInfo,
dwStubFlags & ~NDIRECTSTUB_FL_FOR_NUMPARAMBYTES,
pNMD);
}
return pStubMD;
}
namespace
{
LPVOID NDirectGetEntryPoint(NDirectMethodDesc *pMD, NATIVE_LIBRARY_HANDLE hMod)
{
// GetProcAddress cannot be called while preemptive GC is disabled.
// It requires the OS to take the loader lock.
CONTRACT(LPVOID)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
}
CONTRACT_END;
RETURN pMD->FindEntryPoint(hMod);
}
//---------------------------------------------------------
// Loads the DLL and finds the procaddress for an N/Direct call.
//---------------------------------------------------------
VOID NDirectLink(NDirectMethodDesc *pMD)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
if (pMD->IsClassConstructorTriggeredAtLinkTime())
{
pMD->GetMethodTable()->CheckRunClassInitThrowing();
}
if (pMD->IsQCall())
{
void* pvTarget = (void*)QCallResolveDllImport(pMD->GetEntrypointName());
#ifdef _DEBUG
CONSISTENCY_CHECK_MSGF(pvTarget != nullptr,
("%s::%s is not registered using DllImportEntry macro in qcallentrypoints.cpp",
pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
#endif
pMD->SetNDirectTarget(pvTarget);
return;
}
// Loading unmanaged dlls can trigger dllmains which certainly count as code execution!
pMD->EnsureActive();
{
LPVOID pvTarget = (LPVOID)PInvokeOverride::GetMethodImpl(pMD->GetLibNameRaw(), pMD->GetEntrypointName());
if (pvTarget != NULL)
{
pMD->SetNDirectTarget(pvTarget);
return;
}
}
NATIVE_LIBRARY_HANDLE hmod = NativeLibrary::LoadLibraryFromMethodDesc(pMD);
_ASSERTE(hmod != NULL);
BOOL fSuccess = FALSE;
LPVOID pvTarget = NDirectGetEntryPoint(pMD, hmod);
if (pvTarget)
{
pMD->SetNDirectTarget(pvTarget);
fSuccess = TRUE;
}
if (!fSuccess)
{
StackSString ssLibName(SString::Utf8, pMD->GetLibName());
WCHAR wszEPName[50];
if (MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pMD->GetEntrypointName(), -1, wszEPName, sizeof(wszEPName)/sizeof(WCHAR)) == 0)
{
wszEPName[0] = W('?');
wszEPName[1] = W('\0');
}
#ifdef TARGET_UNIX
COMPlusThrow(kEntryPointNotFoundException, IDS_EE_NDIRECT_GETPROCADDRESS_UNIX, ssLibName.GetUnicode(), wszEPName);
#else
COMPlusThrow(kEntryPointNotFoundException, IDS_EE_NDIRECT_GETPROCADDRESS_WIN, ssLibName.GetUnicode(), wszEPName);
#endif
}
}
}
PCODE NDirect::GetStubForILStub(MethodDesc* pManagedMD, MethodDesc** ppStubMD, DWORD dwStubFlags)
{
CONTRACT(PCODE)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pManagedMD));
POSTCONDITION(RETVAL != NULL);
}
CONTRACT_END;
CONSISTENCY_CHECK(*ppStubMD == NULL);
PInvokeStaticSigInfo sigInfo(pManagedMD);
*ppStubMD = NDirect::CreateCLRToNativeILStub(&sigInfo, dwStubFlags, pManagedMD);
RETURN JitILStub(*ppStubMD);
}
PCODE NDirect::GetStubForILStub(NDirectMethodDesc* pNMD, MethodDesc** ppStubMD, DWORD dwStubFlags)
{
STANDARD_VM_CONTRACT;
PCODE pStub = (PCODE)NULL;
CONSISTENCY_CHECK(*ppStubMD == NULL);
PInvokeStaticSigInfo sigInfo;
NDirect::InitializeSigInfoAndPopulateNDirectMethodDesc(pNMD, &sigInfo);
*ppStubMD = NDirect::GetILStubMethodDesc(pNMD, &sigInfo, dwStubFlags);
if (SF_IsForNumParamBytes(dwStubFlags))
return (PCODE)NULL;
if (*ppStubMD)
{
pStub = JitILStub(*ppStubMD);
}
else
{
CONSISTENCY_CHECK(pNMD->IsVarArgs());
//
// varargs goes through vararg NDirect stub
//
pStub = TheVarargNDirectStub(pNMD->HasRetBuffArg());
}
if (pNMD->IsEarlyBound())
{
pNMD->InitEarlyBoundNDirectTarget();
}
else
{
NDirectLink(pNMD);
}
//
// NOTE: there is a race in updating this MethodDesc. We depend on all
// threads getting back the same DynamicMethodDesc for a particular
// NDirectMethodDesc, in that case, the locking around the actual JIT
// operation will prevent the code from being jitted more than once.
// By the time we get here, all threads get the same address of code
// back from the JIT operation and they all just fill in the same value
// here.
//
// In the NGEN case, all threads will get the same preimplemented code
// address much like the JIT case.
//
return pStub;
}
PCODE JitILStub(MethodDesc* pStubMD)
{
STANDARD_VM_CONTRACT;
PCODE pCode = pStubMD->GetNativeCode();
if (pCode == (PCODE)NULL)
{
///////////////////////////////
//
// Code generation
//
///////////////////////////////
if (pStubMD->IsDynamicMethod())
{
//
// A dynamically generated IL stub
//
pCode = pStubMD->PrepareInitialCode();
_ASSERTE(pCode == pStubMD->GetNativeCode());
}
else
{
//
// A static IL stub that is pointing to a static method in user assembly
// Compile it and return the native code
//
// This returns the stable entry point
pCode = pStubMD->DoPrestub(NULL);
_ASSERTE(pCode == pStubMD->GetStableEntryPoint());
}
}
if (!pStubMD->IsDynamicMethod())
{
// We need an entry point that can be called multiple times
pCode = pStubMD->GetMultiCallableAddrOfCode();
}
return pCode;
}
PCODE GetStubForInteropMethod(MethodDesc* pMD, DWORD dwStubFlags)
{
CONTRACT(PCODE)
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
PRECONDITION(pMD->IsNDirect() || pMD->IsCLRToCOMCall() || pMD->IsEEImpl() || pMD->IsIL());
}
CONTRACT_END;
PCODE pStub = (PCODE)NULL;
MethodDesc* pStubMD = NULL;
if (pMD->IsNDirect())
{
NDirectMethodDesc* pNMD = (NDirectMethodDesc*)pMD;
pStub = NDirect::GetStubForILStub(pNMD, &pStubMD, dwStubFlags);
}
#ifdef FEATURE_COMINTEROP
else
if (pMD->IsCLRToCOMCall())
{
pStub = CLRToCOMCall::GetStubForILStub(pMD, &pStubMD);
}
#endif // FEATURE_COMINTEROP
else
if (pMD->IsEEImpl())
{
CONSISTENCY_CHECK(pMD->GetMethodTable()->IsDelegate());
EEImplMethodDesc* pDelegateMD = (EEImplMethodDesc*)pMD;
pStub = COMDelegate::GetStubForILStub(pDelegateMD, &pStubMD, dwStubFlags);
}
else
if (pMD->IsIL())
{
CONSISTENCY_CHECK(SF_IsReverseStub(dwStubFlags));
pStub = NDirect::GetStubForILStub(pMD, &pStubMD, dwStubFlags);
}
else
{
UNREACHABLE_MSG("unexpected MethodDesc type");
}
if (pStubMD != NULL
&& pStubMD->IsILStub()
&& pStubMD->AsDynamicMethodDesc()->HasFlags(DynamicMethodDesc::FlagRequiresCOM))
{
// the stub uses COM so make sure that it is started
EnsureComStarted();
}
RETURN pStub;
}
#ifdef FEATURE_COMINTEROP
void CreateCLRToDispatchCOMStub(
MethodDesc * pMD,
DWORD dwStubFlags) // NDirectStubFlags
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pMD));
}
CONTRACTL_END;
_ASSERTE(SF_IsCOMLateBoundStub(dwStubFlags) || SF_IsCOMEventCallStub(dwStubFlags));
// If we are dealing with a COM event call, then we need to initialize the
// COM event call information.
if (SF_IsCOMEventCallStub(dwStubFlags))
{
_ASSERTE(pMD->IsCLRToCOMCall()); // no generic COM eventing
((CLRToCOMCallMethodDesc *)pMD)->InitComEventCallInfo();
}
// Get the call signature information
StubSigDesc sigDesc(pMD);
int iLCIDArg = 0;
int numArgs = 0;
int numParamTokens = 0;
mdParamDef* pParamTokenArray = NULL;
CreateNDirectStubAccessMetadata(&sigDesc,
CallConv::GetDefaultUnmanagedCallingConvention(),
&dwStubFlags,
&iLCIDArg,
&numArgs);
numParamTokens = numArgs + 1;
pParamTokenArray = (mdParamDef*)_alloca(numParamTokens * sizeof(mdParamDef));
CollateParamTokens(sigDesc.m_pModule->GetMDImport(), sigDesc.m_tkMethodDef, numArgs, pParamTokenArray);
DispatchStubState MyStubState;
CreateNDirectStubWorker(&MyStubState,
&sigDesc,
(CorNativeLinkType)0,
(CorNativeLinkFlags)0,
CallConv::GetDefaultUnmanagedCallingConvention(),
dwStubFlags | NDIRECTSTUB_FL_COM,
pMD,
pParamTokenArray,
iLCIDArg);
_ASSERTE(pMD->IsCLRToCOMCall()); // no generic disp-calls
((CLRToCOMCallMethodDesc *)pMD)->InitRetThunk();
}
#endif // FEATURE_COMINTEROP
VOID NDirectMethodDesc::SetNDirectTarget(LPVOID pTarget)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(IsNDirect());
PRECONDITION(pTarget != NULL);
}
CONTRACTL_END;
ndirect.m_pNDirectTarget = pTarget;
}
void MarshalStructViaILStub(MethodDesc* pStubMD, void* pManagedData, void* pNativeData, StructMarshalStubs::MarshalOperation operation, void** ppCleanupWorkList /* = nullptr */)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
PRECONDITION(CheckPointer(pStubMD));
}
CONTRACTL_END;
MarshalStructViaILStubCode(pStubMD->GetSingleCallableAddrOfCode(), pManagedData, pNativeData, operation, ppCleanupWorkList);
}
void MarshalStructViaILStubCode(PCODE pStubCode, void* pManagedData, void* pNativeData, StructMarshalStubs::MarshalOperation operation, void** ppCleanupWorkList /* = nullptr */)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
PRECONDITION(pStubCode != NULL);
}
CONTRACTL_END;
PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(pStubCode);
DECLARE_ARGHOLDER_ARRAY(args, 4);
args[ARGNUM_0] = PTR_TO_ARGHOLDER(pManagedData);
args[ARGNUM_1] = PTR_TO_ARGHOLDER(pNativeData);
args[ARGNUM_2] = DWORD_TO_ARGHOLDER(operation);
args[ARGNUM_3] = PTR_TO_ARGHOLDER(ppCleanupWorkList);
CALL_MANAGED_METHOD_NORET(args);
}
//==========================================================================
// This function is reached only via NDirectImportThunk. It's purpose
// is to ensure that the target DLL is fully loaded and ready to run.
//
// FUN FACTS: Though this function is actually entered in unmanaged mode,
// it can reenter managed mode and throw a COM+ exception if the DLL linking
// fails.
//==========================================================================
EXTERN_C LPVOID STDCALL NDirectImportWorker(NDirectMethodDesc* pMD)
{
LPVOID ret = NULL;
BEGIN_PRESERVE_LAST_ERROR;
CONTRACTL
{
THROWS;
GC_TRIGGERS;
MODE_PREEMPTIVE;
}
CONTRACTL_END;
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
// this function is called by CLR to native assembly stubs which are called by
// managed code as a result, we need an unwind and continue handler to translate
// any of our internal exceptions into managed exceptions.
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
if (pMD->IsEarlyBound())
{
// we need the MD to be populated in case we decide to build an intercept
// stub to wrap the target in InitEarlyBoundNDirectTarget
NDirect::PopulateNDirectMethodDesc(pMD);
pMD->InitEarlyBoundNDirectTarget();
}
else
{
//
// Otherwise we're in an inlined pinvoke late bound MD
//
INDEBUG(Thread *pThread = GetThread());
{
_ASSERTE((pThread->GetFrame() != FRAME_TOP && pThread->GetFrame()->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr())
|| pMD->ShouldSuppressGCTransition());
CONSISTENCY_CHECK(pMD->IsNDirect());
//
// With IL stubs, we don't have to do anything but ensure the DLL is loaded.
//
NDirect::PopulateNDirectMethodDesc(pMD);
pMD->CheckRestore();
NDirectLink(pMD);
}
}
ret = pMD->GetNDirectTarget();
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER;
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER;
END_PRESERVE_LAST_ERROR;
return ret;
}
//===========================================================================
// Support for Pinvoke Calli instruction
//
//===========================================================================
EXTERN_C void STDCALL VarargPInvokeStubWorker(TransitionBlock * pTransitionBlock, VASigCookie *pVASigCookie, MethodDesc *pMD)
{
BEGIN_PRESERVE_LAST_ERROR;
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
STATIC_CONTRACT_ENTRY_POINT;
MAKE_CURRENT_THREAD_AVAILABLE();
#ifdef _DEBUG
Thread::ObjectRefFlush(CURRENT_THREAD);
#endif
FrameWithCookie<PrestubMethodFrame> frame(pTransitionBlock, pMD);
PrestubMethodFrame * pFrame = &frame;
pFrame->Push(CURRENT_THREAD);
_ASSERTE(pVASigCookie == pFrame->GetVASigCookie());
_ASSERTE(pMD == pFrame->GetFunction());
GetILStubForCalli(pVASigCookie, pMD);
pFrame->Pop(CURRENT_THREAD);
END_PRESERVE_LAST_ERROR;
}
EXTERN_C void STDCALL GenericPInvokeCalliStubWorker(TransitionBlock * pTransitionBlock, VASigCookie * pVASigCookie, PCODE pUnmanagedTarget)
{
BEGIN_PRESERVE_LAST_ERROR;
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
STATIC_CONTRACT_ENTRY_POINT;
MAKE_CURRENT_THREAD_AVAILABLE();
#ifdef _DEBUG
Thread::ObjectRefFlush(CURRENT_THREAD);
#endif
FrameWithCookie<PInvokeCalliFrame> frame(pTransitionBlock, pVASigCookie, pUnmanagedTarget);
PInvokeCalliFrame * pFrame = &frame;
pFrame->Push(CURRENT_THREAD);
_ASSERTE(pVASigCookie == pFrame->GetVASigCookie());
GetILStubForCalli(pVASigCookie, NULL);
pFrame->Pop(CURRENT_THREAD);
END_PRESERVE_LAST_ERROR;
}
PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD)
{
CONTRACT(PCODE)
{
THROWS;
GC_TRIGGERS;
ENTRY_POINT;
MODE_ANY;
PRECONDITION(CheckPointer(pVASigCookie));
PRECONDITION(CheckPointer(pMD, NULL_OK));
POSTCONDITION(RETVAL != NULL);
}
CONTRACT_END;
PCODE pTempILStub = (PCODE)NULL;
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
// this function is called by CLR to native assembly stubs which are called by
// managed code as a result, we need an unwind and continue handler to translate
// any of our internal exceptions into managed exceptions.
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
// Force a GC if the stress level is high enough
GCStress<cfg_any>::MaybeTrigger();
GCX_PREEMP();
Signature signature = pVASigCookie->signature;
CorInfoCallConvExtension unmgdCallConv = CorInfoCallConvExtension::Managed;
DWORD dwStubFlags = NDIRECTSTUB_FL_BESTFIT;
// The MethodDesc pointer may in fact be the unmanaged target, see PInvokeStubs.asm.
if (pMD == NULL || (UINT_PTR)pMD & 0x1)
{
pMD = NULL;
dwStubFlags |= NDIRECTSTUB_FL_UNMANAGED_CALLI;
// need to convert the CALLI signature to stub signature with managed calling convention
BYTE callConv = MetaSig::GetCallingConvention(signature);
// Unmanaged calling convention indicates modopt should be read
if (callConv != IMAGE_CEE_CS_CALLCONV_UNMANAGED)
{
unmgdCallConv = (CorInfoCallConvExtension)callConv;
}
else
{
CallConvBuilder builder;
UINT errorResID;
HRESULT hr = CallConv::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(pVASigCookie->pModule), signature.GetRawSig(), signature.GetRawSigLen(), &builder, &errorResID);
if (FAILED(hr))
COMPlusThrowHR(hr, errorResID);
unmgdCallConv = builder.GetCurrentCallConv();
if (unmgdCallConv == CallConvBuilder::UnsetValue)
{
unmgdCallConv = CallConv::GetDefaultUnmanagedCallingConvention();
}
if (builder.IsCurrentCallConvModSet(CallConvBuilder::CALL_CONV_MOD_SUPPRESSGCTRANSITION))
{
dwStubFlags |= NDIRECTSTUB_FL_SUPPRESSGCTRANSITION;
}
}
LoaderHeap *pHeap = pVASigCookie->pLoaderModule->GetLoaderAllocator()->GetHighFrequencyHeap();
PCOR_SIGNATURE new_sig = (PCOR_SIGNATURE)(void *)pHeap->AllocMem(S_SIZE_T(signature.GetRawSigLen()));
CopyMemory(new_sig, signature.GetRawSig(), signature.GetRawSigLen());
// make the stub IMAGE_CEE_CS_CALLCONV_DEFAULT
*new_sig &= ~IMAGE_CEE_CS_CALLCONV_MASK;
*new_sig |= IMAGE_CEE_CS_CALLCONV_DEFAULT;
signature = Signature(new_sig, signature.GetRawSigLen());
}
else
{
_ASSERTE(pMD->IsNDirect());
dwStubFlags |= NDIRECTSTUB_FL_CONVSIGASVARARG;
// vararg P/Invoke must be cdecl
unmgdCallConv = CorInfoCallConvExtension::C;
}
mdMethodDef md;
CorNativeLinkFlags nlFlags;
CorNativeLinkType nlType;
if (pMD != NULL)
{
PInvokeStaticSigInfo sigInfo(pMD);
md = pMD->GetMemberDef();
nlFlags = sigInfo.GetLinkFlags();
nlType = sigInfo.GetCharSet();
}
else
{
md = mdMethodDefNil;
nlFlags = nlfNone;
nlType = nltAnsi;
}
StubSigDesc sigDesc(pMD, signature, pVASigCookie->pModule, pVASigCookie->pLoaderModule);
sigDesc.InitTypeContext(pVASigCookie->classInst, pVASigCookie->methodInst);
MethodDesc* pStubMD = NDirect::CreateCLRToNativeILStub(&sigDesc,
nlType,
nlFlags,
unmgdCallConv,
dwStubFlags);
pTempILStub = JitILStub(pStubMD);
InterlockedCompareExchangeT<PCODE>(&pVASigCookie->pNDirectILStub,
pTempILStub,
(PCODE)NULL);
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER;
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER;
RETURN pVASigCookie->pNDirectILStub;
}
#if defined(TARGET_X86) && defined(FEATURE_IJW)
// Copy constructor support for C++/CLI
EXTERN_C void* STDCALL CallCopyConstructorsWorker(void* esp)
{
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_PREEMPTIVE; // we've already switched to preemptive
using ExecuteCallback = void*(STDMETHODCALLTYPE*)(void*);
MethodDesc* pMD = CoreLibBinder::GetMethod(METHOD__COPY_CONSTRUCTOR_CHAIN__EXECUTE_CURRENT_COPIES_AND_GET_TARGET);
ExecuteCallback pExecute = (ExecuteCallback)pMD->GetMultiCallableAddrOfCode();
return pExecute(esp);
}
#endif // defined(TARGET_X86) && defined(FEATURE_IJW)
#endif // #ifndef DACCESS_COMPILE