| File: Windows\Win32\System\Com\ComScope.cs | Web Access |
| Project: src\winforms\src\System.Private.Windows.Core\src\Microsoft.Private.Windows.Core.csproj (Microsoft.Private.Windows.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // .NET Framework struggles with IUnknown in XML comments unless we alias it. using ComUnknown = Windows.Win32.System.Com.IUnknown; namespace Windows.Win32.Foundation; /// <summary> /// Lifetime management struct for a native COM pointer. Meant to be utilized in a <see langword="using"/> statement /// to ensure <see cref="ComUnknown.Release"/> is called when going out of scope with the using. /// </summary> /// <remarks> /// <para> /// This struct has implicit conversions to T** and void** so it can be passed directly to out methods. /// For example: /// </para> /// <code> /// using ComScope<IUnknown> unknown = new(null); /// comObject->QueryInterface(&iid, unknown); /// </code> /// <para> /// Take care to NOT make copies of the struct to avoid accidental over-release. /// </para> /// </remarks> /// <typeparam name="T"> /// This should be one of the struct COM definitions as generated by CsWin32. Ideally we'd constrain to /// <see cref="ComUnknown.Interface"/> or some other interface tag to enforce that this is being used around /// a struct that is actually a COM wrapper. /// </typeparam> internal readonly unsafe ref struct ComScope<T> where T : unmanaged, IComIID { // Keeping internal as nint allows us to use Unsafe methods to get significantly better generated code. private readonly nint _value; public T* Value => (T*)_value; public ComUnknown* AsUnknown => (ComUnknown*)_value; public ComScope(T* value) => _value = (nint)value; // Can't add an operator for IUnknown* as we have ComScope<IUnknown>. public static implicit operator T*(in ComScope<T> scope) => (T*)scope._value; public static implicit operator void*(in ComScope<T> scope) => (void*)scope._value; public static implicit operator nint(in ComScope<T> scope) => scope._value; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T**(in ComScope<T> scope) => (T**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator void**(in ComScope<T> scope) => (void**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); public bool IsNull => _value == 0; /// <summary> /// Tries querying the requested interface into a new <see cref="ComScope{T}"/>. /// </summary> /// <param name="hr">The result of the query.</param> public ComScope<TTo> TryQuery<TTo>(out HRESULT hr) where TTo : unmanaged, IComIID { ComScope<TTo> scope = new(null); hr = ((ComUnknown*)Value)->QueryInterface(IID.Get<TTo>(), scope); return scope; } /// <summary> /// Queries the requested interface into a new <see cref="ComScope{T}"/>. /// </summary> public ComScope<TTo> Query<TTo>() where TTo : unmanaged, IComIID { ComScope<TTo> scope = new(null); ((ComUnknown*)Value)->QueryInterface(IID.Get<TTo>(), scope).ThrowOnFailure(); return scope; } /// <summary> /// Attempt to create a <see cref="ComScope{T}"/> from the given COM interface. /// </summary> public static ComScope<T> TryQueryFrom<TFrom>(TFrom* from, out HRESULT hr) where TFrom : unmanaged, IComIID { ComScope<T> scope = new(null); hr = from is null ? HRESULT.E_POINTER : ((ComUnknown*)from)->QueryInterface(IID.Get<T>(), scope); return scope; } /// <summary> /// Create a <see cref="ComScope{T}"/> from the given COM interface. Throws on failure. /// </summary> public static ComScope<T> QueryFrom<TFrom>(TFrom* from) where TFrom : unmanaged, IComIID { if (from is null) { HRESULT.E_POINTER.ThrowOnFailure(); } ComScope<T> scope = new(null); ((ComUnknown*)from)->QueryInterface(IID.Get<T>(), scope).ThrowOnFailure(); return scope; } /// <summary> /// Simple helper for checking if a given interface is supported. Only use this if you don't intend to /// use the interface, otherwise use <see cref="TryQuery{TTo}(out HRESULT)"/>. /// </summary> public bool SupportsInterface<TInterface>() where TInterface : unmanaged, IComIID { if (typeof(TInterface) == typeof(T)) { return true; } ComUnknown* unknown; HRESULT hr = AsUnknown->QueryInterface(IID.Get<TInterface>(), (void**)&unknown); if (hr.Succeeded) { unknown->Release(); return true; } return false; } public void Dispose() { ComUnknown* unknown = (ComUnknown*)_value; // Really want this to be null after disposal to avoid double releases, but we also want // to maintain the readonly state of the struct to allow passing as `in` without creating implicit // copies (which would break the T** and void** operators). *(void**)this = null; if (unknown is not null) { unknown->Release(); } } }