* Repurpose CleanupWorkList to also preserve delegate references in structs across the full native call.
* Change CleanupWorkListElement to abstract base class instead of interface.
* Make CleanupWorkList a singlely linked list.
* PR Feedback.
* Remove CleanupWorkList and make CleanupWorkListElement be able to represent the full list.
* Add back throw in SafeHandle field marshalling.
* PR feedback.
internal static class ValueClassMarshaler
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern void ConvertToNative(IntPtr dst, IntPtr src, IntPtr pMT, ref CleanupWorkList pCleanupWorkList);
+ internal static extern void ConvertToNative(IntPtr dst, IntPtr src, IntPtr pMT, ref CleanupWorkListElement pCleanupWorkList);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void ConvertToManaged(IntPtr dst, IntPtr src, IntPtr pMT);
private Type layoutType;
// Cleanup list to be destroyed when clearing the native view (for layouts with SafeHandles).
- private CleanupWorkList cleanupWorkList;
+ private CleanupWorkListElement cleanupWorkList;
[Flags]
internal enum AsAnyFlags
#endif
} // struct NativeVariant
+ internal abstract class CleanupWorkListElement
+ {
+ private CleanupWorkListElement m_Next;
+ protected abstract void DestroyCore();
+
+ public void Destroy()
+ {
+ DestroyCore();
+ CleanupWorkListElement next = m_Next;
+ while (next != null)
+ {
+ next.DestroyCore();
+ next = next.m_Next;
+ }
+ }
+
+ public static void AddToCleanupList(ref CleanupWorkListElement list, CleanupWorkListElement newElement)
+ {
+ if (list == null)
+ {
+ list = newElement;
+ }
+ else
+ {
+ newElement.m_Next = list;
+ list = newElement;
+ }
+ }
+ }
+
+ // Keeps a Delegate instance alive across the full Managed->Native call.
+ // This ensures that users don't have to call GC.KeepAlive after passing a struct or class
+ // that has a delegate field to native code.
+ internal sealed class DelegateCleanupWorkListElement : CleanupWorkListElement
+ {
+ public DelegateCleanupWorkListElement(Delegate del)
+ {
+ m_del = del;
+ }
+
+ private Delegate m_del;
+
+ protected override void DestroyCore()
+ {
+ GC.KeepAlive(m_del);
+ }
+ }
+
// Aggregates SafeHandle and the "owned" bit which indicates whether the SafeHandle
// has been successfully AddRef'ed. This allows us to do realiable cleanup (Release)
// if and only if it is needed.
- internal sealed class CleanupWorkListElement
+ internal sealed class SafeHandleCleanupWorkListElement : CleanupWorkListElement
{
- public CleanupWorkListElement(SafeHandle handle)
+ public SafeHandleCleanupWorkListElement(SafeHandle handle)
{
m_handle = handle;
}
- public SafeHandle m_handle;
+ private SafeHandle m_handle;
// This field is passed by-ref to SafeHandle.DangerousAddRef.
- // CleanupWorkList.Destroy ignores this element if m_owned is not set to true.
- public bool m_owned;
- } // class CleanupWorkListElement
-
- internal sealed class CleanupWorkList
- {
- private List<CleanupWorkListElement> m_list = new List<CleanupWorkListElement>();
+ // DestroyCore ignores this element if m_owned is not set to true.
+ private bool m_owned;
- public void Add(CleanupWorkListElement elem)
+ protected override void DestroyCore()
{
- Debug.Assert(elem.m_owned == false, "m_owned is supposed to be false and set later by DangerousAddRef");
- m_list.Add(elem);
+ if (m_owned)
+ StubHelpers.SafeHandleRelease(m_handle);
}
- public void Destroy()
+ public IntPtr AddRef()
{
- for (int i = m_list.Count - 1; i >= 0; i--)
- {
- if (m_list[i].m_owned)
- StubHelpers.SafeHandleRelease(m_list[i].m_handle);
- }
+ // element.m_owned will be true iff the AddRef succeeded
+ return StubHelpers.SafeHandleAddRef(m_handle, ref m_owned);
}
- } // class CleanupWorkList
+ } // class CleanupWorkListElement
internal static class StubHelpers
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void ThrowInteropParamException(int resID, int paramIdx);
- internal static IntPtr AddToCleanupList(ref CleanupWorkList pCleanupWorkList, SafeHandle handle)
+ internal static IntPtr AddToCleanupList(ref CleanupWorkListElement pCleanupWorkList, SafeHandle handle)
{
- if (pCleanupWorkList == null)
- pCleanupWorkList = new CleanupWorkList();
-
- CleanupWorkListElement element = new CleanupWorkListElement(handle);
- pCleanupWorkList.Add(element);
+ SafeHandleCleanupWorkListElement element = new SafeHandleCleanupWorkListElement(handle);
+ CleanupWorkListElement.AddToCleanupList(ref pCleanupWorkList, element);
+ return element.AddRef();
+ }
- // element.m_owned will be true iff the AddRef succeeded
- return SafeHandleAddRef(handle, ref element.m_owned);
+ internal static void AddToCleanupList(ref CleanupWorkListElement pCleanupWorkList, Delegate del)
+ {
+ DelegateCleanupWorkListElement element = new DelegateCleanupWorkListElement(del);
+ CleanupWorkListElement.AddToCleanupList(ref pCleanupWorkList, element);
}
- internal static void DestroyCleanupList(ref CleanupWorkList pCleanupWorkList)
+ internal static void DestroyCleanupList(ref CleanupWorkListElement pCleanupWorkList)
{
if (pCleanupWorkList != null)
{
internal static extern unsafe int strlen(sbyte* ptr);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern unsafe void FmtClassUpdateNativeInternal(object obj, byte* pNative, ref CleanupWorkList pCleanupWorkList);
+ internal static extern unsafe void FmtClassUpdateNativeInternal(object obj, byte* pNative, ref CleanupWorkListElement pCleanupWorkList);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern unsafe void FmtClassUpdateCLRInternal(object obj, byte* pNative);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
SetCleanupNeeded();
// we setup a new local that will hold the cleanup work list
- LocalDesc desc(MscorlibBinder::GetClass(CLASS__CLEANUP_WORK_LIST));
+ LocalDesc desc(MscorlibBinder::GetClass(CLASS__CLEANUP_WORK_LIST_ELEMENT));
m_dwCleanupWorkListLocalNum = NewLocal(desc);
}
}
CONTRACTL_END;
LPVOID fnPtr = COMDelegate::ConvertToCallback(*pCLRValue);
+
+ // If there is no CleanupWorkList (i.e. a call from Marshal.StructureToPtr), we don't use it to manage delegate lifetime.
+ // In that case, it falls on the user to manage the delegate lifetime. This is the cleanest way to do this since there is no well-defined
+ // object lifetime for the unmanaged memory that the structure would be marshalled to in the Marshal.StructureToPtr case.
+ if (*pCLRValue != NULL && ppCleanupWorkListOnStack != NULL)
+ {
+ // Call StubHelpers.AddToCleanupList to ensure the delegate is kept alive across the full native call.
+ MethodDescCallSite AddToCleanupList(METHOD__STUBHELPERS__ADD_TO_CLEANUP_LIST_DELEGATE);
+
+ ARG_SLOT args[] =
+ {
+ (ARG_SLOT)ppCleanupWorkListOnStack,
+ ObjToArgSlot(*pCLRValue)
+ };
+
+ AddToCleanupList.Call(args);
+ }
+
MAYBE_UNALIGNED_WRITE(pNativeValue, _PTR, fnPtr);
}
// A cleanup list MUST be specified in order for us to be able to marshal
// the SafeHandle.
if (ppCleanupWorkListOnStack == NULL)
- COMPlusThrow(kInvalidOperationException, IDS_EE_SH_FIELD_INVALID_OPERATION);
+ COMPlusThrow(kInvalidOperationException, IDS_EE_SH_FIELD_INVALID_OPERATION);
if (*pSafeHandleObj == NULL)
COMPlusThrow(kArgumentNullException, W("ArgumentNull_SafeHandle"));
// Call StubHelpers.AddToCleanupList to AddRef and schedule Release on this SafeHandle
// This is realiable, i.e. the cleanup will happen if and only if the SH was actually AddRef'ed.
- MethodDescCallSite AddToCleanupList(METHOD__STUBHELPERS__ADD_TO_CLEANUP_LIST);
+ MethodDescCallSite AddToCleanupList(METHOD__STUBHELPERS__ADD_TO_CLEANUP_LIST_SAFEHANDLE);
ARG_SLOT args[] =
{
pslILEmit->EmitLDTOKEN(managedVCToken); // pMT
pslILEmit->EmitCALL(METHOD__RT_TYPE_HANDLE__GETVALUEINTERNAL, 1, 1); // Convert RTH to IntPtr
- if (IsCLRToNative(m_dwMarshalFlags))
- {
- // this should only be needed in CLR-to-native scenarios for the SafeHandle field marshaler
- m_pslNDirect->LoadCleanupWorkList(pslILEmit);
- }
- else
- {
- pslILEmit->EmitLoadNullPtr();
- }
-
- pslILEmit->EmitCALL(METHOD__VALUECLASSMARSHALER__CONVERT_TO_NATIVE, 4, 0); // void ConvertToNative(IntPtr dst, IntPtr src, IntPtr pMT, ref CleanupWorkList pCleanupWorkList)
+ m_pslNDirect->LoadCleanupWorkList(pslILEmit);
+ pslILEmit->EmitCALL(METHOD__VALUECLASSMARSHALER__CONVERT_TO_NATIVE, 4, 0); // void ConvertToNative(IntPtr dst, IntPtr src, IntPtr pMT, ref CleanupWorkListElement pCleanupWorkList)
}
void ILValueClassMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit)
EmitLoadManagedValue(pslILEmit);
EmitLoadNativeValue(pslILEmit);
- if (IsCLRToNative(m_dwMarshalFlags))
- {
- m_pslNDirect->LoadCleanupWorkList(pslILEmit);
- }
- else
- {
- //
- // The assertion here is as follows:
- // 1) the only field marshaler that requires the CleanupWorkList is FieldMarshaler_SafeHandle
- // 2) SafeHandle marshaling is disallowed in the native-to-CLR direction, so we'll never see it..
- //
- pslILEmit->EmitLDNULL(); // pass a NULL CleanupWorkList in the native-to-CLR case
- }
+ m_pslNDirect->LoadCleanupWorkList(pslILEmit);
// static void FmtClassUpdateNativeInternal(object obj, byte* pNative, IntPtr pOptionalCleanupList);
pslIL->EmitLDLOC(dwInputHandleLocal);
// This is realiable, i.e. the cleanup will happen if and only if the SH was actually AddRef'ed.
- pslIL->EmitCALL(METHOD__STUBHELPERS__ADD_TO_CLEANUP_LIST, 2, 1);
+ pslIL->EmitCALL(METHOD__STUBHELPERS__ADD_TO_CLEANUP_LIST_SAFEHANDLE, 2, 1);
pslIL->EmitSTLOC(dwNativeHandleLocal);
DEFINE_METASIG(SM(IntPtr_RetStr, I, s))
DEFINE_METASIG(SM(IntPtr_RetBool, I, F))
DEFINE_METASIG(SM(IntPtrIntPtrIntPtr_RetVoid, I I I, v))
-DEFINE_METASIG_T(SM(IntPtrIntPtrIntPtr_RefCleanupWorkList_RetVoid, I I I r(C(CLEANUP_WORK_LIST)), v))
+DEFINE_METASIG_T(SM(IntPtrIntPtrIntPtr_RefCleanupWorkListElement_RetVoid, I I I r(C(CLEANUP_WORK_LIST_ELEMENT)), v))
DEFINE_METASIG_T(SM(RuntimeType_RuntimeMethodHandleInternal_RetMethodBase, C(CLASS) g(METHOD_HANDLE_INTERNAL), C(METHOD_BASE) ))
DEFINE_METASIG_T(SM(RuntimeType_IRuntimeFieldInfo_RetFieldInfo, C(CLASS) C(I_RT_FIELD_INFO), C(FIELD_INFO) ))
DEFINE_METASIG_T(SM(RuntimeType_Int_RetPropertyInfo, C(CLASS) i, C(PROPERTY_INFO) ))
DEFINE_METASIG(SM(Byte_RetChar, b, u))
DEFINE_METASIG(SM(Str_Bool_Bool_RefInt_RetIntPtr, s F F r(i), I))
DEFINE_METASIG(SM(IntPtr_Int_RetStr, I i, s))
-DEFINE_METASIG_T(SM(Obj_PtrByte_RefCleanupWorkList_RetVoid, j P(b) r(C(CLEANUP_WORK_LIST)), v))
+DEFINE_METASIG_T(SM(Obj_PtrByte_RefCleanupWorkListElement_RetVoid, j P(b) r(C(CLEANUP_WORK_LIST_ELEMENT)), v))
DEFINE_METASIG(SM(Obj_PtrByte_RetVoid, j P(b), v))
DEFINE_METASIG(SM(PtrByte_IntPtr_RetVoid, P(b) I, v))
DEFINE_METASIG(SM(Str_Bool_Bool_RefInt_RetArrByte, s F F r(i), a(b) ))
DEFINE_METASIG(GM(RefT_T_T_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(M(0)) M(0) M(0), M(0)))
DEFINE_METASIG(SM(RefObject_Object_Object_RetObject, r(j) j j, j))
-DEFINE_METASIG_T(SM(RefCleanupWorkList_RetVoid, r(C(CLEANUP_WORK_LIST)), v))
-DEFINE_METASIG_T(SM(RefCleanupWorkList_SafeHandle_RetIntPtr, r(C(CLEANUP_WORK_LIST)) C(SAFE_HANDLE), I))
+DEFINE_METASIG_T(SM(RefCleanupWorkListElement_RetVoid, r(C(CLEANUP_WORK_LIST_ELEMENT)), v))
+DEFINE_METASIG_T(SM(RefCleanupWorkListElement_SafeHandle_RetIntPtr, r(C(CLEANUP_WORK_LIST_ELEMENT)) C(SAFE_HANDLE), I))
+DEFINE_METASIG_T(SM(RefCleanupWorkListElement_Delegate_RetVoid, r(C(CLEANUP_WORK_LIST_ELEMENT)) C(DELEGATE), v))
#ifdef FEATURE_ICASTABLE
DEFINE_METASIG_T(SM(ICastable_RtType_RefException_RetBool, C(ICASTABLE) C(CLASS) r(C(EXCEPTION)), F))
DEFINE_METHOD(APP_DOMAIN, SETUP_DOMAIN, SetupDomain, IM_Bool_Str_Str_ArrStr_ArrStr_RetVoid)
DEFINE_METHOD(APP_DOMAIN, INITIALIZE_COMPATIBILITY_FLAGS, InitializeCompatibilityFlags, IM_RetVoid)
-DEFINE_CLASS(CLEANUP_WORK_LIST, StubHelpers, CleanupWorkList)
+DEFINE_CLASS(CLEANUP_WORK_LIST_ELEMENT, StubHelpers, CleanupWorkListElement)
#ifdef FEATURE_COMINTEROP
// Define earlier in mscorlib.h to avoid BinderClassID to const BYTE truncation warning
DEFINE_METHOD(STUBHELPERS, CLEAR_LAST_ERROR, ClearLastError, SM_RetVoid)
DEFINE_METHOD(STUBHELPERS, THROW_INTEROP_PARAM_EXCEPTION, ThrowInteropParamException, SM_Int_Int_RetVoid)
-DEFINE_METHOD(STUBHELPERS, ADD_TO_CLEANUP_LIST, AddToCleanupList, SM_RefCleanupWorkList_SafeHandle_RetIntPtr)
-DEFINE_METHOD(STUBHELPERS, DESTROY_CLEANUP_LIST, DestroyCleanupList, SM_RefCleanupWorkList_RetVoid)
+DEFINE_METHOD(STUBHELPERS, ADD_TO_CLEANUP_LIST_SAFEHANDLE, AddToCleanupList, SM_RefCleanupWorkListElement_SafeHandle_RetIntPtr)
+DEFINE_METHOD(STUBHELPERS, ADD_TO_CLEANUP_LIST_DELEGATE, AddToCleanupList, SM_RefCleanupWorkListElement_Delegate_RetVoid)
+DEFINE_METHOD(STUBHELPERS, DESTROY_CLEANUP_LIST, DestroyCleanupList, SM_RefCleanupWorkListElement_RetVoid)
DEFINE_METHOD(STUBHELPERS, GET_HR_EXCEPTION_OBJECT, GetHRExceptionObject, SM_Int_RetException)
DEFINE_METHOD(STUBHELPERS, CREATE_CUSTOM_MARSHALER_HELPER, CreateCustomMarshalerHelper, SM_IntPtr_Int_IntPtr_RetIntPtr)
DEFINE_METHOD(STUBHELPERS, CHECK_STRING_LENGTH, CheckStringLength, SM_Int_RetVoid)
-DEFINE_METHOD(STUBHELPERS, FMT_CLASS_UPDATE_NATIVE_INTERNAL, FmtClassUpdateNativeInternal, SM_Obj_PtrByte_RefCleanupWorkList_RetVoid)
+DEFINE_METHOD(STUBHELPERS, FMT_CLASS_UPDATE_NATIVE_INTERNAL, FmtClassUpdateNativeInternal, SM_Obj_PtrByte_RefCleanupWorkListElement_RetVoid)
DEFINE_METHOD(STUBHELPERS, FMT_CLASS_UPDATE_CLR_INTERNAL, FmtClassUpdateCLRInternal, SM_Obj_PtrByte_RetVoid)
DEFINE_METHOD(STUBHELPERS, LAYOUT_DESTROY_NATIVE_INTERNAL, LayoutDestroyNativeInternal, SM_PtrByte_IntPtr_RetVoid)
DEFINE_METHOD(STUBHELPERS, ALLOCATE_INTERNAL, AllocateInternal, SM_IntPtr_RetObj)
#endif // FEATURE_COMINTEROP
DEFINE_CLASS(VALUECLASSMARSHALER, StubHelpers, ValueClassMarshaler)
-DEFINE_METHOD(VALUECLASSMARSHALER, CONVERT_TO_NATIVE, ConvertToNative, SM_IntPtrIntPtrIntPtr_RefCleanupWorkList_RetVoid)
+DEFINE_METHOD(VALUECLASSMARSHALER, CONVERT_TO_NATIVE, ConvertToNative, SM_IntPtrIntPtrIntPtr_RefCleanupWorkListElement_RetVoid)
DEFINE_METHOD(VALUECLASSMARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, SM_IntPtrIntPtrIntPtr_RetVoid)
DEFINE_METHOD(VALUECLASSMARSHALER, CLEAR_NATIVE, ClearNative, SM_IntPtr_IntPtr_RetVoid)
[DllImport("PInvoke_Delegate_AsField")]
extern static bool TakeDelegateAsFieldInClass_Seq(Class2_FuncPtrAsField3_Seq s);
+ [DllImport("PInvoke_Delegate_AsField", EntryPoint = "TakeDelegateAsFieldInClass_Seq")]
+ extern static bool TakeDelegateAsFieldInPreMarshalledClass_Seq(IntPtr ptr);
+
[DllImport("PInvoke_Delegate_AsField")]
extern static bool TakeDelegateAsFieldInClass_Exp(Class2_FuncPtrAsField4_Exp s);
c3.dele = new Dele(CommonMethod);
Assert.IsTrue(TakeDelegateAsFieldInClass_Seq(c3), "Delegate marshaled as field in class with Sequential.");
+ Console.WriteLine("\n\nScenario 4: Delegate marshaled as field in pre-marshalled class with Sequential.");
+ Class2_FuncPtrAsField3_Seq c4 = new Class2_FuncPtrAsField3_Seq();
+ c4.verification = true;
+ c4.dele = new Dele(CommonMethod);
+ IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Class2_FuncPtrAsField3_Seq>());
+ Marshal.StructureToPtr(c4, ptr, false);
+ Assert.IsTrue(TakeDelegateAsFieldInPreMarshalledClass_Seq(ptr), "Delegate marshaled as field in pre-marshalled class with Sequential.");
+ Marshal.FreeHGlobal(ptr);
+ GC.KeepAlive(c4);
+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- Console.WriteLine("\n\nScenario 4 : Delegate marshaled as field in class with Explicit.");
- Class2_FuncPtrAsField4_Exp c4 = new Class2_FuncPtrAsField4_Exp();
- c4.verification = true;
- c4.dele = new Dele(CommonMethod);
- Assert.IsTrue(TakeDelegateAsFieldInClass_Exp(c4), "Delegate marshaled as field in class with Explicit.");
+ Console.WriteLine("\n\nScenario 5 : Delegate marshaled as field in class with Explicit.");
+ Class2_FuncPtrAsField4_Exp c5 = new Class2_FuncPtrAsField4_Exp();
+ c5.verification = true;
+ c5.dele = new Dele(CommonMethod);
+ Assert.IsTrue(TakeDelegateAsFieldInClass_Exp(c5), "Delegate marshaled as field in class with Explicit.");
}
return 100;
} catch (Exception e){