From 33dce147de7ba33dee0c0cb007c540b1717eb82b Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Fri, 2 Feb 2024 19:13:48 -0800 Subject: [PATCH] Add non-generic ComScope for handling arbitrary COM objects --- src/thirtytwo/Win32/System/Com/ComScope.cs | 89 +------------ src/thirtytwo/Win32/System/Com/ComScope{T}.cs | 118 ++++++++++++++++++ 2 files changed, 123 insertions(+), 84 deletions(-) create mode 100644 src/thirtytwo/Win32/System/Com/ComScope{T}.cs diff --git a/src/thirtytwo/Win32/System/Com/ComScope.cs b/src/thirtytwo/Win32/System/Com/ComScope.cs index 0d74cfa..6819182 100644 --- a/src/thirtytwo/Win32/System/Com/ComScope.cs +++ b/src/thirtytwo/Win32/System/Com/ComScope.cs @@ -1,107 +1,28 @@ // Copyright (c) Jeremy W. Kuhne. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -// Based on https://github.com/dotnet/winforms/blob/main/src/System.Windows.Forms.Primitives/src/Windows/Win32/Foundation/ComScope.cs -// -// Original header -// --------------- -// 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. - using System.Runtime.CompilerServices; namespace Windows.Win32.System.Com; /// -/// Lifetime management struct for a native COM pointer. Meant to be utilized in a statement -/// to ensure is called when going out of scope with the using. +/// Untyped equivalent of . Prefer . /// -/// -/// -/// This struct has implicit conversions to T** and void** so it can be passed directly to out methods. -/// For example: -/// -/// -/// using ComScope<IUnknown> unknown = new(null); -/// comObject->QueryInterface(&iid, unknown); -/// -/// -/// Take care to NOT make copies of the struct to avoid accidental over-release. -/// -/// -/// -/// This should be one of the struct COM definitions as generated by CsWin32. -/// -public readonly unsafe ref struct ComScope where T : unmanaged, IComIID +public readonly unsafe ref struct ComScope { // 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 ComScope(T* value) => _value = (nint)value; + public void* Value => (void*)_value; public ComScope(void* value) => _value = (nint)value; - public static implicit operator T*(in ComScope scope) => (T*)scope._value; - - public static implicit operator void*(in ComScope scope) => (void*)scope._value; - - public static implicit operator nint(in ComScope scope) => scope._value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator T**(in ComScope scope) => (T**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); + public static implicit operator void*(in ComScope scope) => (void*)scope._value; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator void**(in ComScope scope) => (void**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); + public static implicit operator void**(in ComScope scope) => (void**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); public bool IsNull => _value == 0; - public ComScope QueryInterface() where TInterface : unmanaged, IComIID - { - ComScope scope = new(null); - ((IUnknown*)Value)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); - return scope; - } - - public ComScope TryQueryInterface() where TInterface : unmanaged, IComIID - => TryQueryInterface(out _); - - public ComScope TryQueryInterface(out HRESULT result) where TInterface : unmanaged, IComIID - { - ComScope scope = new(null); - result = ((IUnknown*)Value)->QueryInterface(IID.Get(), scope); - return scope; - } - - public static ComScope TryQueryFrom(TFrom* from, out HRESULT result) where TFrom : unmanaged, IComIID - { - ComScope scope = new(null); - result = from is null ? HRESULT.E_POINTER : ((IUnknown*)from)->QueryInterface(IID.Get(), scope); - return scope; - } - - public static ComScope QueryFrom(TFrom* from) where TFrom : unmanaged, IComIID - { - ComScope scope = new(null); - if (from is null) - { - HRESULT.E_POINTER.ThrowOnFailure(); - } - - ((IUnknown*)from)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); - return scope; - } - - /// - /// Gets a COM callable wrapper (CCW) for the given . - /// - public static ComScope GetComCallableWrapper(object? obj) - => new(ComHelpers.GetComPointer(obj)); - - public static ComScope TryGetComCallableWrapper(object? obj, out HRESULT result) - => new(ComHelpers.TryGetComPointer(obj, out result)); - public void Dispose() { IUnknown* unknown = (IUnknown*)_value; diff --git a/src/thirtytwo/Win32/System/Com/ComScope{T}.cs b/src/thirtytwo/Win32/System/Com/ComScope{T}.cs new file mode 100644 index 0000000..0d74cfa --- /dev/null +++ b/src/thirtytwo/Win32/System/Com/ComScope{T}.cs @@ -0,0 +1,118 @@ +// Copyright (c) Jeremy W. Kuhne. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Based on https://github.com/dotnet/winforms/blob/main/src/System.Windows.Forms.Primitives/src/Windows/Win32/Foundation/ComScope.cs +// +// Original header +// --------------- +// 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. + +using System.Runtime.CompilerServices; + +namespace Windows.Win32.System.Com; + +/// +/// Lifetime management struct for a native COM pointer. Meant to be utilized in a statement +/// to ensure is called when going out of scope with the using. +/// +/// +/// +/// This struct has implicit conversions to T** and void** so it can be passed directly to out methods. +/// For example: +/// +/// +/// using ComScope<IUnknown> unknown = new(null); +/// comObject->QueryInterface(&iid, unknown); +/// +/// +/// Take care to NOT make copies of the struct to avoid accidental over-release. +/// +/// +/// +/// This should be one of the struct COM definitions as generated by CsWin32. +/// +public readonly unsafe ref struct ComScope 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 ComScope(T* value) => _value = (nint)value; + + public ComScope(void* value) => _value = (nint)value; + + public static implicit operator T*(in ComScope scope) => (T*)scope._value; + + public static implicit operator void*(in ComScope scope) => (void*)scope._value; + + public static implicit operator nint(in ComScope scope) => scope._value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T**(in ComScope scope) => (T**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator void**(in ComScope scope) => (void**)Unsafe.AsPointer(ref Unsafe.AsRef(in scope._value)); + + public bool IsNull => _value == 0; + + public ComScope QueryInterface() where TInterface : unmanaged, IComIID + { + ComScope scope = new(null); + ((IUnknown*)Value)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); + return scope; + } + + public ComScope TryQueryInterface() where TInterface : unmanaged, IComIID + => TryQueryInterface(out _); + + public ComScope TryQueryInterface(out HRESULT result) where TInterface : unmanaged, IComIID + { + ComScope scope = new(null); + result = ((IUnknown*)Value)->QueryInterface(IID.Get(), scope); + return scope; + } + + public static ComScope TryQueryFrom(TFrom* from, out HRESULT result) where TFrom : unmanaged, IComIID + { + ComScope scope = new(null); + result = from is null ? HRESULT.E_POINTER : ((IUnknown*)from)->QueryInterface(IID.Get(), scope); + return scope; + } + + public static ComScope QueryFrom(TFrom* from) where TFrom : unmanaged, IComIID + { + ComScope scope = new(null); + if (from is null) + { + HRESULT.E_POINTER.ThrowOnFailure(); + } + + ((IUnknown*)from)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); + return scope; + } + + /// + /// Gets a COM callable wrapper (CCW) for the given . + /// + public static ComScope GetComCallableWrapper(object? obj) + => new(ComHelpers.GetComPointer(obj)); + + public static ComScope TryGetComCallableWrapper(object? obj, out HRESULT result) + => new(ComHelpers.TryGetComPointer(obj, out result)); + + public void Dispose() + { + IUnknown* unknown = (IUnknown*)_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(); + } + } +} \ No newline at end of file