File: System\Runtime\InteropServices\Marshalling\StrategyBasedComWrappers.cs
Web Access
Project: System.Runtime.InteropServices.csproj (System.Runtime.InteropServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;

namespace System.Runtime.InteropServices.Marshalling
{
    /// <summary>
    /// A <see cref="ComWrappers"/>-based type that uses customizable strategy objects to implement COM object wrappers and managed object wrappers exposed to COM.
    /// </summary>
    [UnsupportedOSPlatform("android")]
    [UnsupportedOSPlatform("browser")]
    [UnsupportedOSPlatform("ios")]
    [UnsupportedOSPlatform("tvos")]
    [CLSCompliant(false)]
    public class StrategyBasedComWrappers : ComWrappers
    {
        internal static StrategyBasedComWrappers DefaultMarshallingInstance { get; } = new();

        /// <summary>
        /// The default strategy to discover interface details about COM interfaces.
        /// </summary>
        /// <remarks>
        /// This strategy can discover interfaces and classes that use source-generated COM interop that use the <see cref="GeneratedComInterfaceAttribute"/> and <see cref="GeneratedComClassAttribute"/> attributes.
        /// This strategy looks for an <see cref="IUnknownDerivedAttribute{T, TImpl}"/> or <see cref="ComExposedClassAttribute{T}"/> attribute on the type of the provided object to discover COM type information.
        /// </remarks>
        public static IIUnknownInterfaceDetailsStrategy DefaultIUnknownInterfaceDetailsStrategy { get; } = Marshalling.DefaultIUnknownInterfaceDetailsStrategy.Instance;

        /// <summary>
        /// The default strategy to use for calling <c>IUnknown</c> methods.
        /// </summary>
        /// <remarks>
        /// This strategy expects that all provided COM objects are either free threaded and that calls to <c>IUnknown</c> methods can be made from any thread or affinitized to the current apartment and <c>IUnknown</c> methods can safely be called on the current thread.
        /// This strategy is always safe on non-Windows platforms, since COM apartments do not exist on those platforms.
        /// </remarks>
        public static IIUnknownStrategy DefaultIUnknownStrategy { get; } = FreeThreadedStrategy.Instance;

        /// <summary>
        /// The default strategy to use for caching COM objects.
        /// </summary>
        /// <returns>The default strategy caches the interface pointers per interface no matter what thread they were initially retrieved on.</returns>
        protected static IIUnknownCacheStrategy CreateDefaultCacheStrategy() => new DefaultCaching();

        /// <summary>
        /// Get or create the interface details strategy for a new COM object wrapper.
        /// </summary>
        /// <returns>The interface details strategy to use for the new COM object.</returns>
        protected virtual IIUnknownInterfaceDetailsStrategy GetOrCreateInterfaceDetailsStrategy()
        {
            if (OperatingSystem.IsWindows() && RuntimeFeature.IsDynamicCodeSupported && ComObject.BuiltInComSupported && ComObject.ComImportInteropEnabled)
            {
                return GetInteropStrategy();
            }
            return DefaultIUnknownInterfaceDetailsStrategy;

            // This logic is split into a separate method, otherwise the trimmer will think that these suppressions are unnecessary on various platforms and error on them.
            // The easiest way to handle this is to put the case that needs annotations into a separate method.
            [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "The usage is guarded, but the analyzer and the trimmer don't understand it.")]
            [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "The opt-in feature is documented to not work in trimming scenarios.")]
            static IIUnknownInterfaceDetailsStrategy GetInteropStrategy()
            {
                return ComImportInteropInterfaceDetailsStrategy.Instance;
            }
        }

        /// <summary>
        /// Get or create the IUnknown strategy for a new COM object wrapper.
        /// </summary>
        /// <returns>The IUnknown strategy to use for the new COM object.</returns>
        protected virtual IIUnknownStrategy GetOrCreateIUnknownStrategy() => DefaultIUnknownStrategy;

        /// <summary>
        /// Create the caching strategy for a new COM object wrapper.
        /// </summary>
        /// <returns>The caching strategy to use for the new COM object.</returns>
        protected virtual IIUnknownCacheStrategy CreateCacheStrategy() => CreateDefaultCacheStrategy();

        /// <summary>
        /// Gets or creates a COM representation of the supplied object and queries it for the interface represented by <typeparamref name="TInterface"/>.
        /// </summary>
        /// <typeparam name="TInterface">The managed type that represents the requested COM interface.</typeparam>
        /// <param name="instance">The managed object to expose outside the .NET runtime.</param>
        /// <param name="flags">A bitwise combination of the enumeration values that specifies how to create the COM representation.</param>
        /// <returns>A pointer to the requested COM interface. The caller is responsible for releasing the returned reference.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="instance"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The interface details strategy does not provide details for <typeparamref name="TInterface"/>.</exception>
        /// <exception cref="InvalidCastException">The COM representation does not support the interface represented by <typeparamref name="TInterface"/>.</exception>
        /// <remarks>
        /// The COM representation is cached before querying for the requested interface, even if the query fails.
        /// The requested interface is queried on every call.
        /// </remarks>
        public IntPtr GetOrCreateComInterfaceForObject<TInterface>(object instance, CreateComInterfaceFlags flags)
        {
            ArgumentNullException.ThrowIfNull(instance);

            IIUnknownDerivedDetails? details = GetOrCreateInterfaceDetailsStrategy().GetIUnknownDerivedDetails(typeof(TInterface).TypeHandle);
            if (details is null)
            {
                throw new ArgumentException(SR.Format(SR.Argument_UnknownComInterface, typeof(TInterface)), nameof(TInterface));
            }

            return GetOrCreateComInterfaceForObject(instance, flags, details.Iid);
        }

        /// <inheritdoc cref="ComWrappers.ComputeVtables" />
        protected sealed override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
        {
            if (GetOrCreateInterfaceDetailsStrategy().GetComExposedTypeDetails(obj.GetType().TypeHandle) is { } details)
            {
                return details.GetComInterfaceEntries(out count);
            }
            count = 0;
            return null;
        }

        /// <inheritdoc cref="ComWrappers.CreateObject(nint, CreateObjectFlags)" />
        protected sealed override unsafe object CreateObject(nint externalComObject, CreateObjectFlags flags)
        {
            if (flags.HasFlag(CreateObjectFlags.TrackerObject)
                || flags.HasFlag(CreateObjectFlags.Aggregation))
            {
                throw new NotSupportedException();
            }

            var rcw = new ComObject(GetOrCreateInterfaceDetailsStrategy(), GetOrCreateIUnknownStrategy(), CreateCacheStrategy(), (void*)externalComObject)
            {
                UniqueInstance = flags.HasFlag(CreateObjectFlags.UniqueInstance)
            };

            return rcw;
        }

        /// <inheritdoc cref="ComWrappers.CreateObject(nint, CreateObjectFlags, object?, out CreatedWrapperFlags)" />
        protected sealed override object? CreateObject(nint externalComObject, CreateObjectFlags flags, object? userState, out CreatedWrapperFlags wrapperFlags)
        {
            return base.CreateObject(externalComObject, flags, userState, out wrapperFlags);
        }

        /// <inheritdoc cref="ComWrappers.ReleaseObjects" />
        protected sealed override void ReleaseObjects(IEnumerable objects)
        {
            throw new NotImplementedException();
        }
    }
}