Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
5b3126e
Make delegates immutable
MichalPetryka Mar 3, 2024
64baa1d
Add a comment
MichalPetryka Mar 3, 2024
a489288
Optimize cache checks
MichalPetryka Mar 3, 2024
b0b31bd
Merge branch 'main' into immutable-delegates
MichalPetryka Mar 3, 2024
bc132e4
Fix x86
MichalPetryka Mar 3, 2024
2dce6a8
Fix R2R
MichalPetryka Mar 4, 2024
00fcd02
Update methodcontext.cpp
MichalPetryka Mar 4, 2024
7941123
Fix contract
MichalPetryka Mar 4, 2024
4de2754
Update src/coreclr/vm/object.cpp
MichalPetryka Mar 4, 2024
b9f7f14
Swap fields
MichalPetryka Mar 4, 2024
90c6085
Reorder asserts
MichalPetryka Mar 4, 2024
9509023
Fix contract again
MichalPetryka Mar 4, 2024
557bf4d
GCPROTECT
MichalPetryka Mar 4, 2024
1eab602
Merge branch 'main' into immutable-delegates
MichalPetryka Mar 5, 2024
477a3c5
Merge branch 'main' into immutable-delegates
MichalPetryka Mar 5, 2024
fff595a
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Oct 13, 2024
2c16705
Rework equality checks
MichalPetryka Oct 16, 2024
cbe22f7
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Oct 16, 2024
bb2a509
Fix compile errors
MichalPetryka Oct 16, 2024
3bb047b
Add asserts
MichalPetryka Oct 16, 2024
b957392
Revert some changes, add more caching
MichalPetryka Oct 16, 2024
f3e48e1
Add asserts
MichalPetryka Oct 16, 2024
35e4404
Merge branch 'main' into immutable-delegates
MichalPetryka Oct 17, 2024
8701078
Improve GetHashCode
MichalPetryka Oct 17, 2024
e22a731
Merge branch 'main' into immutable-delegates
MichalPetryka Oct 17, 2024
63cba0a
Fix GetHashCode
MichalPetryka Oct 17, 2024
10964cd
Massage codegen
MichalPetryka Oct 18, 2024
d593b08
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Oct 22, 2024
26b6e61
Apply feedback
MichalPetryka Oct 22, 2024
99e37d6
Overload invocationList for DynamicMethods
MichalPetryka Oct 24, 2024
d9707d2
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka May 10, 2025
b4075c4
Remove newline diff
MichalPetryka May 10, 2025
405309f
Update comdelegate.h
MichalPetryka May 10, 2025
8359913
Merge branch 'main' into immutable-delegates
MichalPetryka Sep 4, 2025
a2185be
Merge branch 'main' into immutable-delegates
MichalPetryka Jan 28, 2026
4d12dc9
Merge branch 'main' into immutable-delegates
MichalPetryka Mar 22, 2026
6caeb42
Update src/coreclr/System.Private.CoreLib/src/System/MulticastDelegat…
MichalPetryka Mar 22, 2026
3492e7d
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Apr 24, 2026
43c2c82
Cleanup
MichalPetryka Apr 24, 2026
179aea1
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Jun 3, 2026
ad5645b
Merge branch 'main' into immutable-delegates
MichalPetryka Jun 9, 2026
e0a52e3
Address review
MichalPetryka Jun 10, 2026
80f6ecc
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Jun 16, 2026
9c7c07d
Remove CWT, cleanup delegate impl
MichalPetryka Jun 17, 2026
29a5ad4
Remove junk
MichalPetryka Jun 17, 2026
96df463
Refactor delegate marker definitions in comdelegate.cpp
MichalPetryka Jun 17, 2026
efc644e
Revert renames
MichalPetryka Jun 17, 2026
8a91b95
Fix comments
MichalPetryka Jun 17, 2026
94a9b25
Revert missed field
MichalPetryka Jun 17, 2026
f33e751
Optimize invocations
MichalPetryka Jun 17, 2026
20bd567
More cleanup
MichalPetryka Jun 17, 2026
15c9c65
Fix multicast equality
MichalPetryka Jun 17, 2026
561c31b
Merge branch 'main' into immutable-delegates
MichalPetryka Jun 18, 2026
c5b3e0b
Merge branch 'main' into immutable-delegates
MichalPetryka Jun 19, 2026
1d4c034
Merge branch 'main' into immutable-delegates
MichalPetryka Jun 19, 2026
45dc5ed
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Jun 19, 2026
68d5505
Address feedback, cleanup
MichalPetryka Jun 19, 2026
262032f
Cleanup more
MichalPetryka Jun 19, 2026
018bc6a
Follow the same pattern as the other places
MichalPetryka Jun 20, 2026
be61178
Fix build
MichalPetryka Jun 20, 2026
9631348
Cleanup native logic
MichalPetryka Jun 20, 2026
5f121f3
Fix build
MichalPetryka Jun 20, 2026
7c103a1
Move methodDesc to invocationCount
MichalPetryka Jun 20, 2026
474c916
Cleanup properties
MichalPetryka Jun 20, 2026
8657011
Merge remote-tracking branch 'upstream/main' into immutable-delegates
MichalPetryka Jun 20, 2026
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
263 changes: 174 additions & 89 deletions src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs

Large diffs are not rendered by default.

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3262,9 +3262,10 @@ DacDbiInterfaceImpl::DelegateType DacDbiInterfaceImpl::GetDelegateType(VMPTR_Obj

DelegateType delegateType = DelegateType::kUnknownDelegateType;
PTR_DelegateObject pDelObj = dac_cast<PTR_DelegateObject>(delegateObject.GetDacPtr());
INT_PTR invocationCount = pDelObj->GetInvocationCount();

if (invocationCount == 0)
INT_PTR invocationCount = pDelObj->GetInvocationCount();
OBJECTREF invocationList = pDelObj->GetInvocationList();
if (((invocationList == NULL) || !invocationList->GetMethodTable()->IsArray()) && (invocationCount != DELEGATE_MARKER_UNMANAGEDFPTR))
{
// If this delegate points to a static function or this is a open virtual delegate, this should be non-null
// Special case: This might fail in a VSD delegate (instance open virtual)...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ private static bool EqualInvocationLists(Wrapper[] a, Wrapper[] b, int start, in
// look at the invocation list.) If this is found we remove it from
// this list and return a new delegate. If its not found a copy of the
// current list is returned.
protected virtual Delegate? RemoveImpl(Delegate d)
protected virtual Delegate? RemoveImpl(Delegate? d)
{
// There is a special case were we are removing using a delegate as
// the value we need to check for this case
Expand Down
182 changes: 47 additions & 135 deletions src/coreclr/vm/comdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@
#include "comcallablewrapper.h"
#endif // FEATURE_COMINTEROP

#define DELEGATE_MARKER_UNMANAGEDFPTR -1


#ifndef DACCESS_COMPILE

#if defined(TARGET_X86)
Expand Down Expand Up @@ -1231,7 +1228,6 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis,
// runtime.
PCODE pTargetCall = GetVirtualCallStub(pTargetMethod, TypeHandle(pExactMethodType));
(*pRefThis)->SetMethodPtrAux(pTargetCall);
(*pRefThis)->SetInvocationCount((INT_PTR)(void *)pTargetMethod);
}
else
{
Expand Down Expand Up @@ -1261,7 +1257,7 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis,
}
else
{
PCODE pTargetCode = (PCODE)NULL;
PCODE pTargetCode = NULL;

// For virtual methods we can (and should) virtualize the call now (so we don't have to insert a thunk to do so at runtime).
// <TODO>
Expand Down Expand Up @@ -1291,10 +1287,13 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis,
(*pRefThis)->SetMethodPtr(pTargetCode);
}

(*pRefThis)->SetInvocationCount((INT_PTR)pTargetMethod);

LoaderAllocator *pLoaderAllocator = pTargetMethod->GetLoaderAllocator();

_ASSERTE((*pRefThis)->GetInvocationList() == NULL);
if (pLoaderAllocator->IsCollectible())
(*pRefThis)->SetMethodBase(pLoaderAllocator->GetExposedObject());
(*pRefThis)->SetInvocationList(pLoaderAllocator->GetExposedObject());
}

// Marshals a delegate to a unmanaged callback.
Expand Down Expand Up @@ -1376,7 +1375,7 @@ LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj)
_ASSERTE(objhnd != NULL);

// This target should not ever be used. We are storing it in the thunk for better diagnostics of "call on collected delegate" crashes.
PCODE pManagedTargetForDiagnostics = (pDelegate->GetMethodPtrAux() != (PCODE)NULL) ? pDelegate->GetMethodPtrAux() : pDelegate->GetMethodPtr();
PCODE pManagedTargetForDiagnostics = pDelegate->GetMethodPtrAux() != (PCODE)NULL ? pDelegate->GetMethodPtrAux() : pDelegate->GetMethodPtr();

// MethodDesc is passed in for profiling to know the method desc of target
pUMEntryThunk->LoadTimeInit(
Expand Down Expand Up @@ -1708,8 +1707,9 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q
if (!isStatic)
methodArgCount++; // count 'this'

_ASSERTE(refThis->GetInvocationList() == NULL);
if (pMeth->GetLoaderAllocator()->IsCollectible())
refThis->SetMethodBase(pMeth->GetLoaderAllocator()->GetExposedObject());
refThis->SetInvocationList(pMeth->GetLoaderAllocator()->GetExposedObject());

// Open delegates.
if (invokeArgCount == methodArgCount)
Expand All @@ -1726,7 +1726,6 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q
{
PCODE pTargetCall = GetVirtualCallStub(pMeth, TypeHandle(pMeth->GetMethodTable()));
refThis->SetMethodPtrAux(pTargetCall);
refThis->SetInvocationCount((INT_PTR)(void *)pMeth);
}
else
{
Expand Down Expand Up @@ -1783,23 +1782,12 @@ extern "C" void QCALLTYPE Delegate_Construct(QCall::ObjectHandleOnStack _this, Q
refThis->SetMethodPtr((PCODE)(void *)method);
}

refThis->SetInvocationCount((INT_PTR)pMeth);

GCPROTECT_END();
END_QCALL;
}

MethodDesc *COMDelegate::GetMethodDescForOpenVirtualDelegate(OBJECTREF orDelegate)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
}
CONTRACTL_END;

return (MethodDesc*)((DELEGATEREF)orDelegate)->GetInvocationCount();
}

MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate)
{
CONTRACTL
Expand All @@ -1812,107 +1800,37 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate)

// If you modify this logic, please update cDAC IObject.GetDelegateInfo.

MethodDesc *pMethodHandle = NULL;

DELEGATEREF thisDel = (DELEGATEREF) orDelegate;
DELEGATEREF innerDel = NULL;

INT_PTR count = thisDel->GetInvocationCount();
if (count != 0)
{
// this is one of the following:
// - multicast - _invocationList is Array && _invocationCount != 0
// - unamanaged ftn ptr - _invocationList == NULL && _invocationCount == -1
// - wrapper delegate - _invocationList is Delegate && _invocationCount != NULL
// - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc)
// or _invocationList points to a LoaderAllocator/DynamicResolver (inner open virtual delegate of a Wrapper Delegate)
// in the wrapper delegate case we want to unwrap and return the method desc of the inner delegate
// in the other cases we return the method desc for the invoke
innerDel = (DELEGATEREF) thisDel->GetInvocationList();
bool fOpenVirtualDelegate = false;

if (innerDel != NULL)
{
MethodTable *pMT = innerDel->GetMethodTable();
if (pMT->IsDelegate())
return GetMethodDesc(innerDel);
if (!pMT->IsArray())
{
// must be a virtual one
fOpenVirtualDelegate = true;
}
}
else
{
if (count != DELEGATE_MARKER_UNMANAGEDFPTR)
{
// must be a virtual one
fOpenVirtualDelegate = true;
}
}
// - unmanaged ftn ptr - _invocationList == NULL && _invocationCount == -1
// - MethodDesc already cached

if (fOpenVirtualDelegate)
pMethodHandle = GetMethodDescForOpenVirtualDelegate(thisDel);
else
pMethodHandle = FindDelegateInvokeMethod(thisDel->GetMethodTable());
}
else
{
// Next, check for an open delegate
PCODE code = thisDel->GetMethodPtrAux();
// we return the method desc for the invoke
OBJECTREF innerDel = thisDel->GetInvocationList();
if ((innerDel != NULL && innerDel->GetMethodTable()->IsArray()) || count == DELEGATE_MARKER_UNMANAGEDFPTR)
return FindDelegateInvokeMethod(thisDel->GetMethodTable());

if (code == (PCODE)NULL)
{
// Must be a normal delegate
code = thisDel->GetMethodPtr();
}

pMethodHandle = NonVirtualEntry2MethodDesc(code);
return (MethodDesc*)count;
}

_ASSERTE(pMethodHandle);
return pMethodHandle;
}

OBJECTREF COMDelegate::GetTargetObject(OBJECTREF obj)
{
CONTRACTL
// Next, check for an open delegate
PCODE code = thisDel->GetMethodPtrAux();
if (code == NULL)
{
THROWS;
GC_NOTRIGGER;
MODE_COOPERATIVE;
// Must be a normal delegate
code = thisDel->GetMethodPtr();
}
CONTRACTL_END;

OBJECTREF targetObject = NULL;

DELEGATEREF thisDel = (DELEGATEREF) obj;
OBJECTREF innerDel = NULL;

if (thisDel->GetInvocationCount() != 0)
{
// this is one of the following:
// - multicast
// - unmanaged ftn ptr
// - wrapper delegate
// - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc)
// or _invocationList points to a LoaderAllocator/DynamicResolver (inner open virtual delegate of a Wrapper Delegate)
// in the wrapper delegate case we want to unwrap and return the object of the inner delegate
innerDel = (DELEGATEREF) thisDel->GetInvocationList();
if (innerDel != NULL)
{
MethodTable *pMT = innerDel->GetMethodTable();
if (pMT->IsDelegate())
{
targetObject = GetTargetObject(innerDel);
}
}
}

if (targetObject == NULL)
targetObject = thisDel->GetTarget();
MethodDesc* pMethodHandle = NonVirtualEntry2MethodDesc(code);
_ASSERTE(pMethodHandle);

return targetObject;
thisDel->SetInvocationCount((INT_PTR)pMethodHandle);
return pMethodHandle;
}

BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate)
Expand All @@ -1927,15 +1845,11 @@ BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate)

BOOL isMulticast = FALSE;

size_t invocationCount = ((DELEGATEREF)delegate)->GetInvocationCount();
if (invocationCount)
OBJECTREF invocationList = ((DELEGATEREF)delegate)->GetInvocationList();
if (invocationList != NULL)
{
OBJECTREF invocationList = ((DELEGATEREF)delegate)->GetInvocationList();
if (invocationList != NULL)
{
MethodTable *pMT = invocationList->GetMethodTable();
isMulticast = pMT->IsArray();
}
MethodTable *pMT = invocationList->GetMethodTable();
isMulticast = pMT->IsArray();
}

return isMulticast;
Expand Down Expand Up @@ -2035,34 +1949,32 @@ void COMDelegate::ThrowIfInvalidUnmanagedCallersOnlyUsage(MethodDesc* pMD)
}

// This method will get the MethodInfo for a delegate
extern "C" void QCALLTYPE Delegate_FindMethodHandle(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack retMethodInfo)
extern "C" void QCALLTYPE Delegate_CreateMethodInfo(MethodDesc* methodDesc, QCall::ObjectHandleOnStack retMethodInfo)
{
QCALL_CONTRACT;

BEGIN_QCALL;

GCX_COOP();

MethodDesc* pMD = COMDelegate::GetMethodDesc(d.Get());
MethodDesc* pMD = methodDesc;
pMD = MethodDesc::FindOrCreateAssociatedMethodDescForReflection(pMD, TypeHandle(pMD->GetMethodTable()), pMD->GetMethodInstantiation());
retMethodInfo.Set(pMD->AllocateStubMethodInfo());

END_QCALL;
}

extern "C" BOOL QCALLTYPE Delegate_InternalEqualMethodHandles(QCall::ObjectHandleOnStack left, QCall::ObjectHandleOnStack right)
extern "C" MethodDesc* QCALLTYPE Delegate_GetMethodDesc(QCall::ObjectHandleOnStack instance)
{
QCALL_CONTRACT;

BOOL fRet = FALSE;
MethodDesc* fRet = nullptr;

BEGIN_QCALL;

GCX_COOP();

MethodDesc* pMDLeft = COMDelegate::GetMethodDesc(left.Get());
MethodDesc* pMDRight = COMDelegate::GetMethodDesc(right.Get());
fRet = pMDLeft == pMDRight;
fRet = COMDelegate::GetMethodDesc(instance.Get());

END_QCALL;

Expand Down Expand Up @@ -2147,7 +2059,7 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat

// Load next delegate from array using LoopCounter as index
pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_LIST)));
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__INVOCATION_LIST)));
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitLDELEM_REF();

Expand All @@ -2171,7 +2083,7 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat
// compare LoopCounter with InvocationCount. If less then branch to nextDelegate
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_COUNT)));
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__INVOCATION_COUNT)));
pCode->EmitBLT(nextDelegate);

// load the return value. return value from the last delegate call is returned
Expand Down Expand Up @@ -2634,8 +2546,8 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT
LoaderAllocator *pTargetMethodLoaderAllocator = pTargetMethod->GetLoaderAllocator();
BOOL isCollectible = pTargetMethodLoaderAllocator->IsCollectible();
// A method that may be instantiated over a collectible type, and is static will require a delegate
// that has the _methodBase field filled in with the LoaderAllocator of the collectible assembly
// associated with the instantiation.
// that has the LoaderAllocator of the collectible assembly associated with the instantiation
// stored in the _invocationList field.
BOOL fMaybeCollectibleAndStatic = FALSE;

if (isStatic)
Expand Down Expand Up @@ -2693,14 +2605,14 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT

// DELEGATE KINDS TABLE
//
// _target _methodPtr _methodPtrAux _invocationList _invocationCount
// _invocationList _target _methodPtr _methodPtrAux
//
// 1- Instance closed 'this' ptr target method null null 0
// 2- Instance open non-virt delegate shuffle thunk target method null 0
// 3- Instance open virtual delegate shuffle thunk Virtual call stub null MethodDesc of target
// 4- Static closed first arg target method null null 0
// 5- Static closed (retbuf) delegate ThisPtrRetBuf precode null null 0
// 6- Static opened delegate shuffle thunk target method null 0
// 1- Instance closed null 'this' ptr target method null
// 2- Instance open non-virt null delegate shuffle thunk target method
// 3- Instance open virtual null delegate Virtual-stub dispatch method id
// 4- Static closed null first arg target method null
// 5- Static closed (special sig) null first arg ThisPtrRetBuf precode null
// 6- Static opened null delegate shuffle thunk target method
//
// Delegate invoke arg count == target method arg count - 2, 3, 6
// Delegate invoke arg count == 1 + target method arg count - 1, 4, 5
Expand All @@ -2712,7 +2624,7 @@ MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pT
// 3 - CtorVirtualDispatch
// Collectible delegates use the corresponding CtorCollectible* variants.
//
// With collectible types, we need to fill the _methodBase field in with a value that represents the LoaderAllocator of the target method
// With collectible types, we need to fill the _invocationList field in with a value that represents the LoaderAllocator of the target method
// if the delegate is not a closed instance delegate.
//
// There are two techniques that will work for this.
Expand Down
7 changes: 2 additions & 5 deletions src/coreclr/vm/comdelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ class COMDelegate
static Stub* GetInvokeMethodStub(EEImplMethodDesc* pMD);

static MethodDesc * __fastcall GetMethodDesc(OBJECTREF obj);
static MethodDesc* GetMethodDescForOpenVirtualDelegate(OBJECTREF orDelegate);
static OBJECTREF GetTargetObject(OBJECTREF obj);

static BOOL IsTrueMulticastDelegate(OBJECTREF delegate);

Expand Down Expand Up @@ -114,10 +112,9 @@ extern "C" BOOL QCALLTYPE Delegate_BindToMethodName(QCall::ObjectHandleOnStack d
extern "C" BOOL QCALLTYPE Delegate_BindToMethodInfo(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack target,
MethodDesc * method, QCall::TypeHandle pMethodType, DelegateBindingFlags flags);

extern "C" void QCALLTYPE Delegate_FindMethodHandle(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack retMethodInfo);

extern "C" BOOL QCALLTYPE Delegate_InternalEqualMethodHandles(QCall::ObjectHandleOnStack left, QCall::ObjectHandleOnStack right);
extern "C" void QCALLTYPE Delegate_CreateMethodInfo(MethodDesc* methodDesc, QCall::ObjectHandleOnStack retMethodInfo);

extern "C" MethodDesc* QCALLTYPE Delegate_GetMethodDesc(QCall::ObjectHandleOnStack instance);

void DistributeEvent(OBJECTREF *pDelegate,
OBJECTREF *pDomain);
Expand Down
Loading
Loading