static void ValidateExternalWrapperCacheCleanUp() { Console.WriteLine($"Running {nameof(ValidateExternalWrapperCacheCleanUp)}..."); var cw = new TestComWrappers(); // Get an object from a tracker runtime. IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject(); // Create a wrapper for the object instance. var weakRef1 = CreateAndRegisterWrapper(cw, trackerObjRaw); // Run the GC to have the wrapper marked for collection. ForceGC(); // Create a new wrapper for the same external object. var weakRef2 = CreateAndRegisterWrapper(cw, trackerObjRaw); // We are using a tracking resurrection WeakReference<T> so we should be able // to get back the objects as they are all continually re-registering for Finalization. Assert.True(weakRef1.TryGetTarget(out ITrackerObjectWrapper wrapper1)); Assert.True(weakRef2.TryGetTarget(out ITrackerObjectWrapper wrapper2)); // Check that the two wrappers aren't equal, meaning we created a new wrapper since // the first wrapper was removed from the internal cache. Assert.NotEqual(wrapper1, wrapper2); // Let the wrappers Finalize. wrapper1.ReregisterForFinalize = false; wrapper2.ReregisterForFinalize = false;
static void ValidateGlobalInstanceScenarios() { Console.WriteLine($"Running {nameof(ValidateGlobalInstanceScenarios)}..."); Console.WriteLine($"Validate RegisterAsGlobalInstance()..."); var wrappers1 = TestComWrappers.Global; wrappers1.RegisterAsGlobalInstance(); Assert.Throws <InvalidOperationException>( () => { wrappers1.RegisterAsGlobalInstance(); }, "Should not be able to re-register for global ComWrappers"); var wrappers2 = new TestComWrappers(); Assert.Throws <InvalidOperationException>( () => { wrappers2.RegisterAsGlobalInstance(); }, "Should not be able to reset for global ComWrappers"); Console.WriteLine($"Validate NotifyEndOfReferenceTrackingOnThread()..."); int hr; var cw = TestComWrappers.Global; // Trigger the thread lifetime end API and verify the callback occurs. hr = MockReferenceTrackerRuntime.Trigger_NotifyEndOfReferenceTrackingOnThread(); Assert.AreEqual(TestComWrappers.ReleaseObjectsCallAck, hr); }
static void ValidateFallbackQueryInterface() { Console.WriteLine($"Running {nameof(ValidateFallbackQueryInterface)}..."); var testObj = new Test() { EnableICustomQueryInterface = true }; var wrappers = new TestComWrappers(); // Allocate a wrapper for the object IntPtr comWrapper = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.None); testObj.ICustomQueryInterface_GetInterfaceResult = new IntPtr(0x2000000); IntPtr result; var anyGuid = new Guid("1E42439C-DCB5-4701-ACBD-87FE92E785DE"); testObj.ICustomQueryInterface_GetInterfaceIID = anyGuid; int hr = Marshal.QueryInterface(comWrapper, ref anyGuid, out result); Assert.AreEqual(hr, 0); Assert.AreEqual(result, testObj.ICustomQueryInterface_GetInterfaceResult); var anyGuid2 = new Guid("7996D0F9-C8DD-4544-B708-0F75C6FF076F"); hr = Marshal.QueryInterface(comWrapper, ref anyGuid2, out result); const int E_NOINTERFACE = unchecked ((int)0x80004002); Assert.AreEqual(hr, E_NOINTERFACE); Assert.AreEqual(result, IntPtr.Zero); }
static void ValidateComInterfaceCreation() { Console.WriteLine($"Running {nameof(ValidateComInterfaceCreation)}..."); var testObj = new Test(); var wrappers = new TestComWrappers(); // Allocate a wrapper for the object IntPtr comWrapper = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); Assert.AreNotEqual(comWrapper, IntPtr.Zero); // Get a wrapper for an object and verify it is the same one. IntPtr comWrapperMaybe = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); Assert.AreEqual(comWrapper, comWrapperMaybe); // Release the wrapper int count = Marshal.Release(comWrapper); Assert.AreEqual(count, 1); count = Marshal.Release(comWrapperMaybe); Assert.AreEqual(count, 0); // Create a new wrapper IntPtr comWrapperNew = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); // Once a wrapper is created for a managed object it is always present Assert.AreEqual(comWrapperNew, comWrapper); // Release the new wrapper count = Marshal.Release(comWrapperNew); Assert.AreEqual(count, 0); }
public DerivedObject(TestComWrappers comWrappersInstance) { IntPtr innerInstance = WeakReferenceNative.CreateAggregatedWeakReferenceObject( comWrappersInstance.GetOrCreateComInterfaceForObject(this, CreateComInterfaceFlags.None)); inner = new WeakReferenceableWrapper(innerInstance, comWrappersInstance.Registration, releaseInFinalizer: false); comWrappersInstance.GetOrRegisterObjectForComInstance(innerInstance, CreateObjectFlags.Aggregation, this); }
static (WeakReference <WeakReferencableWrapper>, IntPtr) GetWeakReference() { var cw = new TestComWrappers(); IntPtr objRaw = WeakReferenceNative.CreateWeakReferencableObject(); var obj = (WeakReferencableWrapper)cw.GetOrCreateObjectForComInstance(objRaw, CreateObjectFlags.None); return(new WeakReference <WeakReferencableWrapper>(obj), objRaw); }
static void ValidateRuntimeTrackerScenario() { Console.WriteLine($"Running {nameof(ValidateRuntimeTrackerScenario)}..."); var cw = new TestComWrappers(); // Get an object from a tracker runtime. IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject(); // Create a managed wrapper for the native object. var trackerObj = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); // Ownership has been transferred to the wrapper. Marshal.Release(trackerObjRaw); var testWrapperIds = new List <int>(); for (int i = 0; i < 1000; ++i) { // Create a native wrapper for the managed object. IntPtr testWrapper = cw.GetOrCreateComInterfaceForObject(new Test(), CreateComInterfaceFlags.TrackerSupport); // Pass the managed object to the native object. int id = trackerObj.AddObjectRef(testWrapper); // Retain the managed object wrapper ptr. testWrapperIds.Add(id); Marshal.Release(testWrapper); } Assert.IsTrue(testWrapperIds.Count <= Test.InstanceCount); GC.Collect(); GC.Collect(); GC.Collect(); GC.Collect(); GC.Collect(); Assert.IsTrue(testWrapperIds.Count <= Test.InstanceCount); // Remove the managed object ref from the native object. foreach (int id in testWrapperIds) { trackerObj.DropObjectRef(id); } testWrapperIds.Clear(); GC.Collect(); GC.Collect(); GC.Collect(); GC.Collect(); GC.Collect(); }
static void ValidateAggregationWithComObject() { Console.WriteLine($"Running {nameof(ValidateAggregationWithComObject)}..."); using var allocTracker = MockReferenceTrackerRuntime.CountTrackerObjectAllocations(); var cw = new TestComWrappers(); WeakReference <Derived> weakRef = Derived.AllocateAndUseBaseType(cw, aggregateRefTracker: false); ForceGC(); // Validate all instances were cleaned up Assert.IsFalse(weakRef.TryGetTarget(out _)); Assert.AreEqual(0, allocTracker.GetCount()); }
static (WeakReference <WeakReferencableWrapper>, IntPtr) GetWeakReference() { var cw = new TestComWrappers(); IntPtr objRaw = WeakReferenceNative.CreateWeakReferencableObject(); var obj = (WeakReferencableWrapper)cw.GetOrCreateObjectForComInstance(objRaw, CreateObjectFlags.None); // The returned WeakReferencableWrapper from ComWrappers takes ownership // of the ref returned from CreateWeakReferencableObject. // Call Marshal.AddRef to ensure that objRaw owns a reference. Marshal.AddRef(objRaw); return(new WeakReference <WeakReferencableWrapper>(obj), objRaw); }
static void ValidateWrappersInstanceIsolation() { Console.WriteLine($"Running {nameof(ValidateWrappersInstanceIsolation)}..."); var cw1 = new TestComWrappers(); var cw2 = new TestComWrappers(); var testObj = new Test(); // Allocate a wrapper for the object IntPtr comWrapper1 = cw1.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); IntPtr comWrapper2 = cw2.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); Assert.AreNotEqual(comWrapper1, IntPtr.Zero); Assert.AreNotEqual(comWrapper2, IntPtr.Zero); Assert.AreNotEqual(comWrapper1, comWrapper2); IntPtr comWrapper3 = cw1.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); IntPtr comWrapper4 = cw2.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); Assert.AreNotEqual(comWrapper3, comWrapper4); Assert.AreEqual(comWrapper1, comWrapper3); Assert.AreEqual(comWrapper2, comWrapper4); Marshal.Release(comWrapper1); Marshal.Release(comWrapper2); Marshal.Release(comWrapper3); Marshal.Release(comWrapper4); // Get an object from a tracker runtime. IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject(); // Create objects for the COM instance var trackerObj1 = (ITrackerObjectWrapper)cw1.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); var trackerObj2 = (ITrackerObjectWrapper)cw2.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); Assert.AreNotEqual(trackerObj1, trackerObj2); var trackerObj3 = (ITrackerObjectWrapper)cw1.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); var trackerObj4 = (ITrackerObjectWrapper)cw2.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); Assert.AreNotEqual(trackerObj3, trackerObj4); Assert.AreEqual(trackerObj1, trackerObj3); Assert.AreEqual(trackerObj2, trackerObj4); Marshal.Release(trackerObjRaw); }
private static void ValidateNativeWeakReference(TestComWrappers cw) { Console.WriteLine($" -- Validate weak reference creation"); var(weakRef, nativeRef) = GetWeakReference(cw); // Make sure RCW is collected GC.Collect(); GC.WaitForPendingFinalizers(); // Non-globally registered ComWrappers instances do not support rehydration. // A weak reference to an RCW wrapping an IWeakReference can stay alive if the RCW was created through // a global ComWrappers instance. If the RCW was created throug a local ComWrappers instance, the weak // reference should be dead and stay dead once the RCW is collected. bool supportsRehydration = cw.Registration != WrapperRegistration.Local; Console.WriteLine($" -- Validate RCW recreation"); ValidateWeakReferenceState(weakRef, expectedIsAlive: supportsRehydration, cw); // Release the last native reference. Marshal.Release(nativeRef); GC.Collect(); GC.WaitForPendingFinalizers(); // After all native references die and the RCW is collected, the weak reference should be dead and stay dead. Console.WriteLine($" -- Validate release"); ValidateWeakReferenceState(weakRef, expectedIsAlive: false); // Reset the weak reference target Console.WriteLine($" -- Validate target reset"); nativeRef = SetWeakReferenceTarget(weakRef, cw); // Make sure RCW is collected GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine($" -- Validate RCW recreation"); ValidateWeakReferenceState(weakRef, expectedIsAlive: supportsRehydration, cw); // Release the last native reference. Marshal.Release(nativeRef); GC.Collect(); GC.WaitForPendingFinalizers(); // After all native references die and the RCW is collected, the weak reference should be dead and stay dead. Console.WriteLine($" -- Validate release"); ValidateWeakReferenceState(weakRef, expectedIsAlive: false); }
static void ValidateQueryInterfaceAfterManagedObjectCollected() { Console.WriteLine($"Running {nameof(ValidateQueryInterfaceAfterManagedObjectCollected)}..."); var cw = new TestComWrappers(); { // Activate the Reference Tracker system in the .NET runtime by consuming an IReferenceTracker instance. IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject(); var trackerObj = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); Marshal.Release(trackerObjRaw); } int refCount; IntPtr refTrackerTarget; { // Create a native wrapper over a managed object. IntPtr testWrapper = CreateWrapper(cw); refTrackerTarget = MockReferenceTrackerRuntime.TrackerTarget_AddRefFromReferenceTrackerAndReturn(testWrapper); // Ownership has been transferred to the IReferenceTrackerTarget instance. // The COM reference count should be 0 and indicates to the GC the managed object // can be collected. refCount = Marshal.Release(testWrapper); Assert.AreEqual(0, refCount); } ForceGC(); // Calling QueryInterface on an IReferenceTrackerTarget instance is permitted when // the wrapper lifetime has been extended. However, the QueryInterface may fail // if the associated managed object was collected. The failure here is an important // part of the contract for a Reference Tracker runtime. var iid = typeof(ITest).GUID; IntPtr iTestComObject; int hr = Marshal.QueryInterface(refTrackerTarget, ref iid, out iTestComObject); const int COR_E_ACCESSING_CCW = unchecked ((int)0x80131544); Assert.AreEqual(COR_E_ACCESSING_CCW, hr); // Release the IReferenceTrackerTarget instance. refCount = MockReferenceTrackerRuntime.TrackerTarget_ReleaseFromReferenceTracker(refTrackerTarget); Assert.AreEqual(0, refCount);
static void ValidateSuppliedInnerNotAggregation() { Console.WriteLine($"Running {nameof(ValidateSuppliedInnerNotAggregation)}..."); var cw = new TestComWrappers(); // Attempt to register a non-zero instance with a non-zero inner value without // indicating the scenario is aggregaion. var invalidInstance = new IntPtr(1); var invalidInner = new IntPtr(2); Assert.Throws <InvalidOperationException>( () => { cw.GetOrRegisterObjectForComInstance(invalidInstance, CreateObjectFlags.None, new object(), invalidInner); }); }
static void ValidateAggregationWithReferenceTrackerObject() { Console.WriteLine($"Running {nameof(ValidateAggregationWithReferenceTrackerObject)}..."); using var allocTracker = MockReferenceTrackerRuntime.CountTrackerObjectAllocations(); var cw = new TestComWrappers(); WeakReference <Derived> weakRef = Derived.AllocateAndUseBaseType(cw, aggregateRefTracker: true); ForceGC(); // Validate all instances were cleaned up. Assert.IsFalse(weakRef.TryGetTarget(out _)); // Reference counter cleanup requires additional GCs since the Finalizer is used // to clean up the Reference Tracker runtime references. ForceGC(); Assert.AreEqual(0, allocTracker.GetCount()); }
static void ValidatePrecreatedExternalWrapper() { Console.WriteLine($"Running {nameof(ValidatePrecreatedExternalWrapper)}..."); var cw = new TestComWrappers(); // Get an object from a tracker runtime. IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject(); // Manually create a wrapper var iid = typeof(ITrackerObject).GUID; IntPtr iTestComObject; int hr = Marshal.QueryInterface(trackerObjRaw, ref iid, out iTestComObject); Assert.AreEqual(hr, 0); var nativeWrapper = new ITrackerObjectWrapper(iTestComObject); // Register wrapper, but supply the wrapper. var nativeWrapper2 = (ITrackerObjectWrapper)cw.GetOrRegisterObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject, nativeWrapper); Assert.AreEqual(nativeWrapper, nativeWrapper2); // Ownership has been transferred to the wrapper. Marshal.Release(trackerObjRaw); // Validate reuse of a wrapper fails. IntPtr trackerObjRaw2 = MockReferenceTrackerRuntime.CreateTrackerObject(); Assert.Throws <NotSupportedException>( () => { cw.GetOrRegisterObjectForComInstance(trackerObjRaw2, CreateObjectFlags.None, nativeWrapper2); }); Marshal.Release(trackerObjRaw2); // Validate passing null wrapper fails. Assert.Throws <ArgumentNullException>( () => { cw.GetOrRegisterObjectForComInstance(trackerObjRaw, CreateObjectFlags.None, null); }); }
static void ValidateCreateObjectCachingScenario() { Console.WriteLine($"Running {nameof(ValidateCreateObjectCachingScenario)}..."); var cw = new TestComWrappers(); // Get an object from a tracker runtime. IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject(); var trackerObj1 = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); var trackerObj2 = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject); Assert.AreEqual(trackerObj1, trackerObj2); // Ownership has been transferred to the wrapper. Marshal.Release(trackerObjRaw); var trackerObj3 = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject | CreateObjectFlags.UniqueInstance); Assert.AreNotEqual(trackerObj1, trackerObj3); }
static void ValidateComInterfaceCreationRoundTrip() { Console.WriteLine($"Running {nameof(ValidateComInterfaceCreationRoundTrip)}..."); var testObj = new Test(); var wrappers = new TestComWrappers(); // Allocate a wrapper for the object IntPtr comWrapper = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.None); Assert.AreNotEqual(IntPtr.Zero, comWrapper); var testObjUnwrapped = wrappers.GetOrCreateObjectForComInstance(comWrapper, CreateObjectFlags.Unwrap); Assert.AreEqual(testObj, testObjUnwrapped); // Release the wrapper int count = Marshal.Release(comWrapper); Assert.AreEqual(0, count); }
static void ValidateIUnknownImpls() => TestComWrappers.ValidateIUnknownImpls();
static IntPtr CreateWrapper(TestComWrappers cw) { return(cw.GetOrCreateComInterfaceForObject(new Test(), CreateComInterfaceFlags.TrackerSupport)); }
private static void ValidateWeakReferenceState(WeakReference <WeakReferenceableWrapper> wr, bool expectedIsAlive, TestComWrappers sourceWrappers = null) { WeakReferenceableWrapper target; bool isAlive = wr.TryGetTarget(out target); Assert.AreEqual(expectedIsAlive, isAlive); if (isAlive && sourceWrappers != null) { Assert.AreEqual(sourceWrappers.Registration, target.Registration); } }
private static (WeakReference <WeakReferenceableWrapper>, IntPtr) GetWeakReference(TestComWrappers cw) { IntPtr objRaw = WeakReferenceNative.CreateWeakReferencableObject(); var obj = (WeakReferenceableWrapper)cw.GetOrCreateObjectForComInstance(objRaw, CreateObjectFlags.None); var wr = new WeakReference <WeakReferenceableWrapper>(obj); ValidateWeakReferenceState(wr, expectedIsAlive: true, cw); return(wr, objRaw); }
private static IntPtr SetWeakReferenceTarget(WeakReference <WeakReferenceableWrapper> wr, TestComWrappers cw) { IntPtr objRaw = WeakReferenceNative.CreateWeakReferencableObject(); var obj = (WeakReferenceableWrapper)cw.GetOrCreateObjectForComInstance(objRaw, CreateObjectFlags.None); wr.SetTarget(obj); ValidateWeakReferenceState(wr, expectedIsAlive: true, cw); return(objRaw); }