Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,156 @@ internal static void SetPendingExceptionObject(Exception? exception)
[SupportedOSPlatform("windows")]
internal static object GetIEnumeratorToEnumVariantMarshaler() => EnumeratorToEnumVariantMarshaler.GetInstance(string.Empty);

private const int DispatchExPropertyCanRead = 1;
private const int DispatchExPropertyCanWrite = 2;

[SupportedOSPlatform("windows")]
[UnmanagedCallersOnly]
private static unsafe void GetDispatchExPropertyFlags(PropertyInfo* pMemberInfo, int* pResult, Exception* pException)
{
try
{
int result = 0;
PropertyInfo property = *pMemberInfo;
if (property.CanRead)
{
result |= DispatchExPropertyCanRead;
}

if (property.CanWrite)
{
result |= DispatchExPropertyCanWrite;
}

*pResult = result;
}
catch (Exception ex)
{
*pException = ex;
}
}

[SupportedOSPlatform("windows")]
[UnmanagedCallersOnly]
private static unsafe void CallICustomQueryInterface(ICustomQueryInterface* pObject, Guid* pIid, IntPtr* ppObject, int* pResult, Exception* pException)
{
try
{
*pResult = (int)pObject->GetInterface(ref *pIid, out *ppObject);
}
catch (Exception ex)
{
*pException = ex;
}
}

private static unsafe ulong InvokeArgSlotMethodWithOneArg(object target, RuntimeMethodHandle methodHandle, nint methodEntryPoint, object? arg0)
{
IRuntimeMethodInfo methodInfo = methodHandle.GetMethodInfo();
Signature signature = new(methodInfo, RuntimeMethodHandle.GetDeclaringType(methodInfo));
RuntimeType returnType = signature.ReturnType;
Debug.Assert(methodEntryPoint != 0);

if (returnType == typeof(void))
{
((delegate*<object, object?, void>)methodEntryPoint)(target, arg0);
return 0;
}

if (returnType == typeof(bool))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you come up with this list of supported return types?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return-type list was not based on a separate runtime contract; it was a conservative subset of ARG_SLOT-sized primitives chosen while removing InvokeMethod from this path.

Copy link
Member

@jkotas jkotas Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not following. Why are byte, double or enums missing in the list? is it not possible for COM events to return these types? It looks suspect to me that there is nint in the list, but no byte.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reduced the full set of types after your comment: #125508 (comment). I think we need some test coverage for non-void types under src/test/Inerop/COM before modifying it further.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to understand the exact set of the types that this supports currently.

{
return ((delegate*<object, object?, bool>)methodEntryPoint)(target, arg0) ? 1UL : 0UL;
}

if (returnType == typeof(int))
{
return unchecked((ulong)((delegate*<object, object?, int>)methodEntryPoint)(target, arg0));
}

if (returnType == typeof(uint))
{
return ((delegate*<object, object?, uint>)methodEntryPoint)(target, arg0);
}

if (returnType == typeof(long))
{
return unchecked((ulong)((delegate*<object, object?, long>)methodEntryPoint)(target, arg0));
}

if (returnType == typeof(ulong))
{
return ((delegate*<object, object?, ulong>)methodEntryPoint)(target, arg0);
}

if (returnType == typeof(IntPtr))
{
return (ulong)(nuint)((delegate*<object, object?, nint>)methodEntryPoint)(target, arg0);
}

if (returnType == typeof(UIntPtr))
{
return (ulong)((delegate*<object, object?, nuint>)methodEntryPoint)(target, arg0);
}

throw new NotSupportedException();
}

[SupportedOSPlatform("windows")]
[UnmanagedCallersOnly]
private static unsafe void InvokeConnectionPointProviderMethod(
object* pProvider,
IntPtr pProviderMethodPtr,
object* pDelegate,
IntPtr pDelegateCtorMethodPtr,
object* pSubscriber,
IntPtr pEventMethodCodePtr,
bool useUIntPtrCtor,
Exception* pException)
{
try
{
nint delegateCtorMethodEntryPoint = (nint)pDelegateCtorMethodPtr;
Debug.Assert(delegateCtorMethodEntryPoint != 0);

// Construct the delegate before invoking the provider method.
if (useUIntPtrCtor)
{
((delegate*<object, object?, nuint, void>)delegateCtorMethodEntryPoint)(*pDelegate, *pSubscriber, (nuint)pEventMethodCodePtr);
}
else
{
((delegate*<object, object?, nint, void>)delegateCtorMethodEntryPoint)(*pDelegate, *pSubscriber, (nint)pEventMethodCodePtr);
}
Comment on lines +1607 to +1614
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (useUIntPtrCtor)
{
((delegate*<object, object?, nuint, void>)delegateCtorMethodEntryPoint)(*pDelegate, *pSubscriber, (nuint)pEventMethodCodePtr);
}
else
{
((delegate*<object, object?, nint, void>)delegateCtorMethodEntryPoint)(*pDelegate, *pSubscriber, (nint)pEventMethodCodePtr);
}
((delegate*<object, object?, nint, void>)delegateCtorMethodEntryPoint)(*pDelegate, *pSubscriber, (nint)pEventMethodCodePtr);

I think it would be ok to always call this using nint signature. We do that in other places with delegate constructors - delegate constructors are implemented using DelegateConstruct that takes IntPtr.


nint providerMethodEntryPoint = (nint)pProviderMethodPtr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nint and IntPtr are the same type. Are these casts really needed?

Debug.Assert(providerMethodEntryPoint != 0);
((delegate*<object, object?, void>)providerMethodEntryPoint)(*pProvider, *pDelegate);
}
catch (Exception ex)
{
*pException = ex;
}
}

[SupportedOSPlatform("windows")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:Value passed to parameter cannot be statically determined", Justification = "The runtime passes a RuntimeType describing the COM event provider. The dynamic constructor access requirements are enforced by runtime callsite semantics.")]
[UnmanagedCallersOnly]
// pResult is an unmanaged ARG_SLOT* (see vm/callhelpers.h). ARG_SLOT is always 8 bytes,
// so we use ulong purely as a fixed-width bit container, not for numeric semantics.
private static unsafe void InvokeClrToComEventProviderMethod(__ComObject* pComObject, RuntimeType* pProviderType, IntPtr pMethodDesc, IntPtr pMethodEntryPoint, Delegate* pEventHandler, ulong* pResult, Exception* pException)
{
try
{
object eventProvider = pComObject->GetEventProvider(*pProviderType);
RuntimeMethodHandle methodHandle = RuntimeMethodHandle.FromIntPtr(pMethodDesc);
*pResult = InvokeArgSlotMethodWithOneArg(eventProvider, methodHandle, (nint)pMethodEntryPoint, *pEventHandler);
}
catch (Exception ex)
{
*pException = ex;
}
}

[SupportedOSPlatform("windows")]
[UnmanagedCallersOnly]
private static unsafe void GetIEnumeratorToEnumVariantMarshaler(object* pResult, Exception* pException)
Expand Down
22 changes: 2 additions & 20 deletions src/coreclr/vm/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -1468,16 +1468,6 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW!
GetOptionalFields()->m_pCoClassForIntf = th;
}

OBJECTHANDLE GetOHDelegate()
{
LIMITED_METHOD_CONTRACT;
return m_ohDelegate;
}
void SetOHDelegate (OBJECTHANDLE _ohDelegate)
{
LIMITED_METHOD_CONTRACT;
m_ohDelegate = _ohDelegate;
}
// Set the COM interface type.
CorIfaceAttr GetComInterfaceType()
{
Expand Down Expand Up @@ -1688,16 +1678,8 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW!
PTR_MethodDescChunk m_pChunks;

#ifdef FEATURE_COMINTEROP
union
{
// For CLR wrapper objects that extend an unmanaged class, this field
// may contain a delegate to be called to allocate the aggregated
// unmanaged class (instead of using CoCreateInstance).
OBJECTHANDLE m_ohDelegate;

// For interfaces this contains the COM interface type.
CorIfaceAttr m_ComInterfaceType;
};
// For interfaces this contains the COM interface type.
CorIfaceAttr m_ComInterfaceType;

ComCallWrapperTemplate *m_pccwTemplate; // points to interop data structures used when this type is exposed to COM
#endif // FEATURE_COMINTEROP
Expand Down
26 changes: 10 additions & 16 deletions src/coreclr/vm/clrtocomcall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,9 @@ UINT32 CLRToCOMEventCallWorker(CLRToCOMMethodFrame* pFrame, CLRToCOMCallMethodDe

struct {
OBJECTREF EventProviderTypeObj;
OBJECTREF EventProviderObj;
OBJECTREF ThisObj;
} gc;
gc.EventProviderTypeObj = NULL;
gc.EventProviderObj = NULL;
gc.ThisObj = NULL;

LOG((LF_STUBS, LL_INFO1000, "Calling CLRToCOMEventCallWorker %s::%s \n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
Expand All @@ -316,32 +314,28 @@ UINT32 CLRToCOMEventCallWorker(CLRToCOMMethodFrame* pFrame, CLRToCOMCallMethodDe
gc.EventProviderTypeObj = pEvProvMT->GetManagedClassObject();
gc.ThisObj = pFrame->GetThis();

UnmanagedCallersOnlyCaller getEventProvider(METHOD__COM_OBJECT__GET_EVENT_PROVIDER);

// Retrieve the event provider for the event interface type.
getEventProvider.InvokeThrowing(&gc.ThisObj, &gc.EventProviderTypeObj, &gc.EventProviderObj);

// Set up an arg iterator to retrieve the arguments from the frame.
MetaSig mSig(pMD);
ArgIterator ArgItr(&mSig);

// Make the call on the event provider method desc.
MethodDescCallSite eventProvider(pEvProvMD, &gc.EventProviderObj);

// Retrieve the event handler passed in.
OBJECTREF EventHandlerObj = ObjectToOBJECTREF(*(Object**)(pFrame->GetTransitionBlock() + ArgItr.GetNextOffset()));

ARG_SLOT EventMethArgs[] =
{
ObjToArgSlot(gc.EventProviderObj),
ObjToArgSlot(EventHandlerObj)
};
ARG_SLOT eventProviderResult = 0;
UnmanagedCallersOnlyCaller invokeClrToComEventProviderMethod(METHOD__STUBHELPERS__INVOKE_CLR_TO_COM_EVENT_PROVIDER_METHOD);
invokeClrToComEventProviderMethod.InvokeThrowing(
&gc.ThisObj,
&gc.EventProviderTypeObj,
(INT_PTR)pEvProvMD,
(INT_PTR)pEvProvMD->GetMultiCallableAddrOfCode(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can use GetSingleCallableAddrOfCode() since the function pointer is used exactly once.

&EventHandlerObj,
&eventProviderResult);

//
// If this can ever return something bigger than an INT64 byval
// then this code is broken. Currently, however, it cannot.
//
*(ARG_SLOT *)(pFrame->GetReturnValuePtr()) = eventProvider.Call_RetArgSlot(EventMethArgs);
*(ARG_SLOT *)(pFrame->GetReturnValuePtr()) = eventProviderResult;

// The COM event call worker does not support value returned in
// floating point registers.
Expand Down
41 changes: 6 additions & 35 deletions src/coreclr/vm/comcallablewrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2340,22 +2340,13 @@ VOID __stdcall InvokeICustomQueryInterfaceGetInterface_CallBack(LPVOID ptr)

GCPROTECT_BEGIN(pObj);

// 1. Get MD
MethodDesc *pMD = pArgs->pWrap->GetSimpleWrapper()->GetComCallWrapperTemplate()->GetICustomQueryInterfaceGetInterfaceMD();
INT_PTR queriedInterface = reinterpret_cast<INT_PTR>(*pArgs->ppUnk);
INT32 result = static_cast<INT32>(CustomQueryInterfaceResult::NotHandled);
UnmanagedCallersOnlyCaller callICustomQueryInterface(METHOD__STUBHELPERS__CALL_ICUSTOM_QUERY_INTERFACE);
callICustomQueryInterface.InvokeThrowing(&pObj, pArgs->pGuid, &queriedInterface, &result);

// 2. Get Object Handle
OBJECTHANDLE hndCustomQueryInterface = pArgs->pWrap->GetObjectHandle();

// 3 construct the MethodDescCallSite
MethodDescCallSite GetInterface(pMD, hndCustomQueryInterface);

ARG_SLOT Args[] = {
ObjToArgSlot(pObj),
PtrToArgSlot(pArgs->pGuid),
PtrToArgSlot(pArgs->ppUnk),
};

*(pArgs->pRetVal) = (CustomQueryInterfaceResult)GetInterface.Call_RetArgSlot(Args);
*pArgs->ppUnk = reinterpret_cast<IUnknown*>(queriedInterface);
*(pArgs->pRetVal) = (CustomQueryInterfaceResult)result;
GCPROTECT_END();
}
}
Expand Down Expand Up @@ -4674,7 +4665,6 @@ ComCallWrapperTemplate* ComCallWrapperTemplate::CreateTemplate(TypeHandle thClas
pTemplate->m_pClassComMT = NULL; // Defer setting this up.
pTemplate->m_pBasicComMT = NULL;
pTemplate->m_pDefaultItf = NULL;
pTemplate->m_pICustomQueryInterfaceGetInterfaceMD = NULL;
pTemplate->m_flags = 0;

// Determine the COM visibility of classes in our hierarchy.
Expand Down Expand Up @@ -4794,7 +4784,6 @@ ComCallWrapperTemplate *ComCallWrapperTemplate::CreateTemplateForInterface(Metho
pTemplate->m_pClassComMT = NULL;
pTemplate->m_pBasicComMT = NULL;
pTemplate->m_pDefaultItf = pItfMT;
pTemplate->m_pICustomQueryInterfaceGetInterfaceMD = NULL;
pTemplate->m_flags = enum_RepresentsVariantInterface;

// Initialize the one ComMethodTable
Expand Down Expand Up @@ -4928,24 +4917,6 @@ ComMethodTable *ComCallWrapperTemplate::SetupComMethodTableForClass(MethodTable
}


MethodDesc * ComCallWrapperTemplate::GetICustomQueryInterfaceGetInterfaceMD()
{
CONTRACT (MethodDesc*)
{
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(m_flags & enum_ImplementsICustomQueryInterface);
}
CONTRACT_END;

if (m_pICustomQueryInterfaceGetInterfaceMD == NULL)
m_pICustomQueryInterfaceGetInterfaceMD = m_thClass.GetMethodTable()->GetMethodDescForInterfaceMethod(
CoreLibBinder::GetMethod(METHOD__ICUSTOM_QUERYINTERFACE__GET_INTERFACE),
TRUE /* throwOnConflict */);
RETURN m_pICustomQueryInterfaceGetInterfaceMD;
}

//--------------------------------------------------------------------------
// Module* ComCallMethodDesc::GetModule()
// Get Module
Expand Down
3 changes: 0 additions & 3 deletions src/coreclr/vm/comcallablewrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,6 @@ class ComCallWrapperTemplate
// Sets up the class method table for the IClassX and also lays it out.
static ComMethodTable *SetupComMethodTableForClass(MethodTable *pMT, BOOL bLayOutComMT);

MethodDesc * GetICustomQueryInterfaceGetInterfaceMD();

BOOL HasInvisibleParent()
{
LIMITED_METHOD_CONTRACT;
Expand Down Expand Up @@ -328,7 +326,6 @@ class ComCallWrapperTemplate
enum_IsSafeTypeForMarshalling = 0x2000, // The class can be safely marshalled out of process via DCOM
};
DWORD m_flags;
MethodDesc* m_pICustomQueryInterfaceGetInterfaceMD;
ULONG m_cbInterfaces;
SLOT* m_rgpIPtr[1];
};
Expand Down
Loading
Loading