|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Interop;
internal readonly struct WeakComHandle<THandle, TObject>
where THandle : class
where TObject : class, THandle
{
// NOTE: The logic here is a little bit tricky. We can't just keep a WeakReference to
// something like a ComHandle, since it's not something that our clients keep alive.
// instead, we keep a weak reference to the inner managed object, which we know will
// always be alive if the outer aggregate is alive. We can't just keep a WeakReference
// to the RCW for the outer object either, since in cases where we have a DCOM or native
// client, the RCW will be cleaned up, even though there is still a native reference
// to the underlying native outer object.
//
// Instead we make use of an implementation detail of the way the CLR's COM aggregation
// works. Namely, if all references to the aggregated object are released, the CLR
// responds to QI's for IUnknown with a different object. So, we store the original
// value, when we know that we have a client, and then we use that to compare to see
// if we still have a client alive.
//
// NOTE: This is _NOT_ AddRef'd. We use it just to store the integer value of the
// IUnknown for comparison purposes.
private readonly WeakReference _managedObjectWeakReference;
private readonly IntPtr _pUnkOfInnerUnknownWhenAlive;
public WeakComHandle(THandle comAggregateObject)
{
if (comAggregateObject == null)
{
_managedObjectWeakReference = null;
_pUnkOfInnerUnknownWhenAlive = IntPtr.Zero;
}
var pUnk = IntPtr.Zero;
var managedObject = ComAggregate.GetManagedObject<TObject>(comAggregateObject);
try
{
pUnk = Marshal.GetIUnknownForObject(managedObject);
_pUnkOfInnerUnknownWhenAlive = pUnk;
}
finally
{
if (pUnk != IntPtr.Zero)
{
Marshal.Release(pUnk);
}
}
_managedObjectWeakReference = new WeakReference(managedObject);
}
public WeakComHandle(ComHandle<THandle, TObject> handle)
{
var pUnk = IntPtr.Zero;
try
{
pUnk = Marshal.GetIUnknownForObject(handle.Object);
_pUnkOfInnerUnknownWhenAlive = pUnk;
}
finally
{
if (pUnk != IntPtr.Zero)
{
Marshal.Release(pUnk);
}
}
_managedObjectWeakReference = new WeakReference(handle.Object);
}
public THandle ComAggregateObject
{
get
{
// This is pretty fragile code, watch carefully for race conditions!
var pUnk = IntPtr.Zero;
try
{
if (_managedObjectWeakReference == null)
{
return null;
}
// Copy target locally to make sure other thread won't delete it before we use it
var target = _managedObjectWeakReference.Target;
if (target == null)
{
return null;
}
pUnk = Marshal.GetIUnknownForObject(target);
if (pUnk == _pUnkOfInnerUnknownWhenAlive)
{
// QueryInterface on COM aggregate might fail during shutdown, so we
// defensively use "as" instead of casting (see Dev10 816848).
return Marshal.GetObjectForIUnknown(pUnk) as THandle;
}
else
{
return null;
}
}
finally
{
if (pUnk != IntPtr.Zero)
{
Marshal.Release(pUnk);
}
}
}
}
internal bool TryGetManagedObjectWithoutCaringWhetherNativeObjectIsAlive(out TObject managedObject)
{
// NOTE: Only use this method if you do NOT care whether the native ComAggregate
// object has already been released.
if (_managedObjectWeakReference == null)
{
managedObject = null;
return false;
}
managedObject = _managedObjectWeakReference.Target as TObject;
return managedObject != null;
}
public ComHandle<THandle, TObject>? ComHandle
{
get
{
var rcw = this.ComAggregateObject;
if (rcw == null)
{
return null;
}
Debug.Assert(_managedObjectWeakReference != null);
if (_managedObjectWeakReference.Target is TObject managedObject)
{
// Construct a new ComHandle without going through the cycle of unwrapping
// the managed object from the rcw, that has shown to be a perf concern for
// progression (see Dev10 Bug 628992).
return new ComHandle<THandle, TObject>(rcw, managedObject);
}
else
{
// We fall back to trying to unwrap the managed object out of the rcw, but
// the Weakref to the managed object shouldn't go null if the rcw is still
// alive, should it?
Debug.Fail("Can this really happen?");
return new ComHandle<THandle, TObject>(rcw);
}
}
}
public bool IsAlive()
{
if (_managedObjectWeakReference == null)
{
return false;
}
return _managedObjectWeakReference.IsAlive;
}
}
|