diff --git a/nuget/Microsoft.Windows.CsWinRT.targets b/nuget/Microsoft.Windows.CsWinRT.targets index 661c3d027..b36783bc9 100644 --- a/nuget/Microsoft.Windows.CsWinRT.targets +++ b/nuget/Microsoft.Windows.CsWinRT.targets @@ -568,14 +568,10 @@ $(CsWinRTInternalProjection) - true - false - true - true - true true true false + true false - + - - - - - - - - - - - - - - - - - + @@ -629,6 +597,11 @@ $(CsWinRTInternalProjection) + + + + /// Converts an unmanaged to a managed . + /// + /// The unmanaged value. + /// The managed value + public static global::System.Type? ConvertToManaged(Type value) + { + if (!WindowsRuntimeFeatureSwitches.EnableXamlTypeMarshalling) + { + TypeExceptions.ThrowNotSupportedExceptionForMarshallingDisabled(); + } + + ReadOnlySpan typeName = HStringMarshaller.ConvertToManagedUnsafe(value.Name); + + // Get the alternate lookup from the cache first and use it to try to retrieve a cached 'Type' + // instance. This allows us to avoid materializing the 'string' if we have a cache hit here. + if (TypeNameCache.TypeNameToTypeMap.GetAlternateLookup().TryGetValue( + key: new TransientTypeReference(typeName, value.Kind), + value: out global::System.Type? result)) + { + return result; + } + + // We didn't get a cached value, so we just manually marshal the value here. Note that we + // can't just use a 'GetOrAdd' overload here like above, because there isn't one available + // that supports alternate lookups. But we don't want to give up on avoiding this allocation. + result = UncachedTypeMarshaller.FromTransientTypeReference(new TransientTypeReference(typeName, value.Kind)); + + // Try to add the value to the cache. If another thread already added it, we just ignore the result. + // Marshalled 'Type' instances are guaranteed to have a 1:1 mapping, so we can still use the local + // value that we just produced, instead of having to do another lookup to get it from the cache. + _ = TypeNameCache.TypeNameToTypeMap.TryAdd(new ManagedTypeReference(typeName.ToString(), value.Kind), result); + + return result; + } + + /// + public static WindowsRuntimeObjectReferenceValue BoxToUnmanaged(global::System.Type? value) + { + return value is null ? default : new((void*)WindowsRuntimeComWrappers.Default.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.None, in WellKnownWindowsInterfaceIIDs.IID_IReferenceOfType)); + } + + /// + public static global::System.Type? UnboxToManaged(void* value) + { + Type? abi = WindowsRuntimeValueTypeMarshaller.UnboxToManaged(value); + + return abi.HasValue ? ConvertToManaged(abi.GetValueOrDefault()) : null; + } + + /// + /// Disposes resources associated with an unmanaged value. + /// + /// The unmanaged value to dispose. + public static void Dispose(Type value) + { + HStringMarshaller.Free(value.Name); + } +} + +/// +/// Represents a reference to a value, for fast marshalling to native. +/// +file readonly struct ManagedTypeReference +{ + /// + public readonly string Name; + + /// + public readonly TypeKind Kind; + + /// + /// Creates a new value with the specified parameters. + /// + /// + /// + public ManagedTypeReference(string name, TypeKind kind) + { + Name = name; + Kind = kind; + } +} + +/// +/// Represents a transient reference to a value, to avoid allocations. +/// +file readonly ref struct TransientTypeReference +{ + /// + public readonly ReadOnlySpan Name; + + /// + public readonly TypeKind Kind; + + /// + /// Creates a new value with the specified parameters. + /// + /// + /// + public TransientTypeReference(ReadOnlySpan name, TypeKind kind) + { + Name = name; + Kind = kind; + } +} + +/// +/// A custom for to support zero-allocation lookups. +/// +file sealed class ManagedTypeReferenceEqualityComparer : + IEqualityComparer, + IAlternateEqualityComparer +{ + /// + /// The singleton instance. + /// + public static readonly ManagedTypeReferenceEqualityComparer Instance = new(); + + /// + public ManagedTypeReference Create(TransientTypeReference alternate) + { + return new(alternate.Name.ToString(), alternate.Kind); + } + + /// + public bool Equals(ManagedTypeReference x, ManagedTypeReference y) + { + return x.Kind == y.Kind && string.Equals(x.Name, y.Name, StringComparison.Ordinal); + } + + /// + public bool Equals(TransientTypeReference alternate, ManagedTypeReference other) + { + return alternate.Kind == other.Kind && alternate.Name.SequenceEqual(other.Name); + } + + /// + public int GetHashCode(ManagedTypeReference obj) + { + return HashCode.Combine( + value1: string.GetHashCode(obj.Name), + value2: obj.Kind); + } + + /// + public int GetHashCode(TransientTypeReference alternate) + { + return HashCode.Combine( + value1: string.GetHashCode(alternate.Name), + value2: alternate.Kind); + } +} + +/// +/// Cached maps to speedup marshalling. +/// +file static class TypeNameCache +{ + /// + /// The cache of type names to instances. + /// + /// + /// This cache is mostly only used by XAML, meaning it should pretty much always be accessed from the UI thread. + /// Because of this, we can set the concurrency level to just '1', to reduce the memory use from this dictionary. + /// + public static readonly ConcurrentDictionary TypeNameToTypeMap = new( + concurrencyLevel: 1, + capacity: 32, + comparer: ManagedTypeReferenceEqualityComparer.Instance); + + /// + /// The cache of instances to type name values. + /// + /// + public static readonly ConcurrentDictionary TypeToTypeNameMap = new(concurrencyLevel: 1, capacity: 32); +} + +/// +/// Marshaller for using no cache. +/// +file static class UncachedTypeMarshaller +{ + /// + /// Converts a to a value. + /// + /// The value. + /// The value. + public static ManagedTypeReference ToManagedTypeReference(global::System.Type value) + { // Special case for 'NoMetadataTypeInfo' instances, which can only be obtained // from previous calls to 'ConvertToManaged' for types that had been trimmed. if (value is NoMetadataTypeInfo noMetadataTypeInfo) { - reference = new TypeReference { Name = noMetadataTypeInfo.FullName, Kind = TypeKind.Metadata }; - - return; + return new(noMetadataTypeInfo.FullName, TypeKind.Metadata); } // We need special handling for 'Nullable' values. If we have one, we want to use the underlying type @@ -127,9 +324,7 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR // Additionally, this path isn't taken if we have a nullable value type, which avoids the lookup too. if (!value.IsGenericType && value.IsDefined(typeof(WindowsRuntimeMetadataAttribute))) { - reference = new TypeReference { Name = value.FullName, Kind = TypeKind.Metadata }; - - return; + return new(value.FullName!, TypeKind.Metadata); } // Use the metadata info lookup first to handle custom-mapped interface types. These would not have a proxy @@ -137,26 +332,20 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR // being projected types from there. So we handle them here first to get the right metadata type name. if (WindowsRuntimeMetadataInfo.TryGetInfo(value, out WindowsRuntimeMetadataInfo? metadataInfo)) { - reference = new TypeReference { Name = metadataInfo.GetMetadataTypeName(), Kind = TypeKind.Metadata }; - - return; + return new(metadataInfo.GetMetadataTypeName(), TypeKind.Metadata); } } // Special case 'Exception' types, since we also need to handle all derived types (e.g. user-defined) if (value.IsAssignableTo(typeof(global::System.Exception))) { - reference = new TypeReference { Name = "Windows.Foundation.HResult", Kind = TypeKind.Metadata }; - - return; + return new("Windows.Foundation.HResult", TypeKind.Metadata); } // Special case 'Type' as well, for the same reason (e.g. 'typeof(Foo)' would return a 'RuntimeType' instance) if (value.IsAssignableTo(typeof(global::System.Type))) { - reference = new TypeReference { Name = "Windows.UI.Xaml.Interop.TypeName", Kind = TypeKind.Metadata }; - - return; + return new("Windows.UI.Xaml.Interop.TypeName", TypeKind.Metadata); } global::System.Type typeOrUnderlyingType = nullableUnderlyingType ?? value; @@ -181,9 +370,7 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR // This will also handle generic delegate types, which will also use '[WindowsRuntimeMetadataTypeName]'. if (marshallingInfo.TryGetMetadataTypeName(out string? metadataTypeName)) { - reference = new TypeReference { Name = metadataTypeName, Kind = kind }; - - return; + return new(metadataTypeName, kind); } // If the type is 'KeyValuePair<,>', we are guaranteed to have a runtime class name on the proxy type. @@ -194,9 +381,7 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR typeOrUnderlyingType.IsGenericType && typeOrUnderlyingType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { - reference = new TypeReference { Name = marshallingInfo.GetRuntimeClassName(), Kind = kind }; - - return; + return new(marshallingInfo.GetRuntimeClassName(), kind); } // If we don't have a metadata type name, check if we have a value type or a delegate type. @@ -209,9 +394,7 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR if (typeOrUnderlyingType.IsValueType || typeOrUnderlyingType.IsAssignableTo(typeof(Delegate))) { - reference = new TypeReference { Name = typeOrUnderlyingType.FullName, Kind = kind }; - - return; + return new(typeOrUnderlyingType.FullName!, kind); } } @@ -230,16 +413,12 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR // cases such as constructed 'Nullable' types, which will report their boxed type name. if (marshallingInfo.TryGetRuntimeClassName(out string? runtimeClassName)) { - reference = new TypeReference { Name = runtimeClassName, Kind = kind }; - - return; + return new(runtimeClassName, kind); } // Otherwise, use the type name directly. This will handle all remaining cases, such as projected // runtime classes and interface types. For all of those, the projected type name will be correct. - reference = new TypeReference { Name = typeOrUnderlyingType.FullName, Kind = kind }; - - return; + return new(typeOrUnderlyingType.FullName!, kind); } // For primitive types, we always report 'TypeKind.Primitive'. This means that some @@ -250,26 +429,24 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR // custom types, which they would be otherwise, since they don't have marshalling info. if (value.IsPrimitive) { - reference = new TypeReference { Name = value.FullName, Kind = TypeKind.Primitive }; - - return; + return new(value.FullName!, TypeKind.Primitive); } CustomType: // All other cases are treated as custom types (e.g. user-defined types) - reference = new TypeReference { Name = value.AssemblyQualifiedName, Kind = TypeKind.Custom }; + return new(value.AssemblyQualifiedName!, TypeKind.Custom); } /// - /// Converts an unmanaged to a managed . + /// Converts a to a managed . /// - /// The unmanaged value. + /// The value. /// The managed value [UnconditionalSuppressMessage("Trimming", "IL2057", Justification = "Any types which are trimmed are not used by managed user code and there is fallback logic to handle that.")] - public static global::System.Type? ConvertToManaged(Type value) + public static global::System.Type? FromTransientTypeReference(TransientTypeReference value) { - ReadOnlySpan typeName = HStringMarshaller.ConvertToManagedUnsafe(value.Name); + ReadOnlySpan typeName = value.Name; // Just return 'null' if we somehow received a default value if (typeName.IsEmpty) @@ -333,7 +510,7 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR publicType.IsAssignableTo(typeof(global::System.Exception)) || publicType.IsAssignableTo(typeof(global::System.Type))) { - return NoMetadataTypeInfo.GetOrCreate(typeName); + return new NoMetadataTypeInfo(typeName.ToString()); } if (publicType.IsValueType) @@ -375,35 +552,12 @@ public static void ConvertToUnmanagedUnsafe(global::System.Type value, out TypeR // returned implementation will just throw an exception for all unsupported operations on it. if (type is null && value.Kind is TypeKind.Metadata) { - return NoMetadataTypeInfo.GetOrCreate(typeName); + return new NoMetadataTypeInfo(typeName.ToString()); } // Return whatever result we managed to get from the cache return type; } - - /// - public static WindowsRuntimeObjectReferenceValue BoxToUnmanaged(global::System.Type? value) - { - return value is null ? default : new((void*)WindowsRuntimeComWrappers.Default.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.None, in WellKnownWindowsInterfaceIIDs.IID_IReferenceOfType)); - } - - /// - public static global::System.Type? UnboxToManaged(void* value) - { - Type? abi = WindowsRuntimeValueTypeMarshaller.UnboxToManaged(value); - - return abi.HasValue ? ConvertToManaged(abi.GetValueOrDefault()) : null; - } - - /// - /// Disposes resources associated with an unmanaged value. - /// - /// The unmanaged value to dispose. - public static void Dispose(Type value) - { - HStringMarshaller.Free(value.Name); - } } /// @@ -425,6 +579,19 @@ public static void ThrowArgumentExceptionForNullType(Type type) $"to be removed. To work around the issue, consider using the '[DynamicDependency]' attribute over the method causing this exception to eventually be thrown. " + $"You can see the API docs for this attribute here: https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.dynamicdependencyattribute."); } + + /// + /// Throws a if marshalling support is disabled. + /// + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowNotSupportedExceptionForMarshallingDisabled() + { + throw new NotSupportedException( + $"Support for marshalling 'System.Type' values is disabled (make sure that the 'CsWinRTEnableXamlTypeMarshalling' property is not set to 'false'). " + + $"In this configuration, marshalling a 'System.Type' value directly to native code or to managed will always fail. Additionally, marshalling a " + + $"boxed 'System.Type' object as an untyped parameter for a Windows Runtime API will result in the CCW using the same layout as for 'object'."); + } } /// @@ -591,18 +758,6 @@ private static HRESULT get_Value(void* thisPtr, Type* result) /// file sealed class NoMetadataTypeInfo : TypeInfo { - /// - /// The cache of instances. - /// - /// - /// This cache is mostly only used by XAML, meaning it should pretty much always be accessed from the UI thread. - /// Because of this, we can set the concurrency level to just '1', to reduce the memory use from this dictionary. - /// - private static readonly ConcurrentDictionary NoMetadataTypeCache = new( - concurrencyLevel: 1, - capacity: 32, - comparer: StringComparer.Ordinal); - /// /// The full name of the type missing metadata information. /// @@ -612,32 +767,11 @@ private static HRESULT get_Value(void* thisPtr, Type* result) /// Creates a new instance with the specified parameters. /// /// The full name of the type missing metadata information. - private NoMetadataTypeInfo(string fullName) + public NoMetadataTypeInfo(string fullName) { _fullName = fullName; } - /// - /// Gets a cached instance for the specified type name. - /// - /// The full name of the type missing metadata information. - /// The resulting instance. - public static NoMetadataTypeInfo GetOrCreate(ReadOnlySpan fullName) - { - // Try to lookup an existing instance first, to skip allocating a 'string' if we can - if (NoMetadataTypeCache.GetAlternateLookup>().TryGetValue(fullName, out NoMetadataTypeInfo? existing)) - { - return existing; - } - - NoMetadataTypeInfo typeInfo = new(fullName.ToString()); - - // The type instance was not in the cache, so try to add it now. We perform this lookup - // with the 'string' instance we created to initialize the new 'NoMetadataTypeInfo' value - // we'trying to add to the cache, so that if we win the race, we only allocate it once. - return NoMetadataTypeCache.GetOrAdd(typeInfo._fullName, typeInfo); - } - /// public override Assembly Assembly => throw new NotSupportedException(); diff --git a/src/WinRT.Runtime2/InteropServices/TypeMapInfo/DynamicInterfaceCastableImplementationInfo.cs b/src/WinRT.Runtime2/InteropServices/TypeMapInfo/DynamicInterfaceCastableImplementationInfo.cs index 0e7001101..aaec47730 100644 --- a/src/WinRT.Runtime2/InteropServices/TypeMapInfo/DynamicInterfaceCastableImplementationInfo.cs +++ b/src/WinRT.Runtime2/InteropServices/TypeMapInfo/DynamicInterfaceCastableImplementationInfo.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -25,7 +26,7 @@ internal sealed class DynamicInterfaceCastableImplementationInfo /// /// The table of marshalling info for all types that can participate in marshalling. /// - private static readonly ConditionalWeakTable TypeToImplementationInfoTable = []; + private static readonly ConcurrentDictionary TypeToImplementationInfoTable = []; /// /// Cached creation factory for . diff --git a/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMarshallingInfo.cs b/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMarshallingInfo.cs index 5bedf6606..06c31b568 100644 --- a/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMarshallingInfo.cs +++ b/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMarshallingInfo.cs @@ -56,7 +56,7 @@ internal sealed class WindowsRuntimeMarshallingInfo /// This will only have non values for types needing special marshalling. Types which are meant to /// be marshalled as opaque IInspectable objects will have no associated values, and should be handled separately. /// - private static readonly ConditionalWeakTable TypeToMarshallingInfoTable = []; + private static readonly ConcurrentDictionary TypeToMarshallingInfoTable = []; /// /// Cached creation factory for . @@ -466,12 +466,19 @@ public static WindowsRuntimeMarshallingInfo GetOpaqueInfo(object instance) return GetInfo(typeof(Exception)); } - // Special case for 'Type' instances too. This is needed even without considering custom user-defined types - // (which shouldn't really be common anyway), because 'Type' itself is just a base type and not instantiated. - // That is, when e.g. doing 'typeof(Foo)', the actual object is some 'RuntimeType' object itself (non public). - if (instance is Type) + // Only enable this marshalling if the feature switch is enabled, to minimize size. Supporting 'Type' + // marshalling actually roots a significant amount of additional code, such as the metadata type map. + // We check the feature switch first to allow the 'Type' cast itself to be trimmed as well. This is + // something that can actually impact binary size, since it will root the type map entries for 'Type'. + if (WindowsRuntimeFeatureSwitches.EnableXamlTypeMarshalling) { - return GetInfo(typeof(Type)); + // Special case for 'Type' instances too. This is needed even without considering custom user-defined types + // (which shouldn't really be common anyway), because 'Type' itself is just a base type and not instantiated. + // That is, when e.g. doing 'typeof(Foo)', the actual object is some 'RuntimeType' object itself (non public). + if (instance is Type) + { + return GetInfo(typeof(Type)); + } } // For all other cases, we fallback to the marshalling info for 'object'. This is the diff --git a/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMetadataInfo.cs b/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMetadataInfo.cs index b8f13152f..f9f2e444f 100644 --- a/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMetadataInfo.cs +++ b/src/WinRT.Runtime2/InteropServices/TypeMapInfo/WindowsRuntimeMetadataInfo.cs @@ -36,7 +36,7 @@ internal sealed class WindowsRuntimeMetadataInfo /// /// This will only have non values for types needing special metadata handling. /// - private static readonly ConditionalWeakTable TypeToMetadataInfoTable = []; + private static readonly ConcurrentDictionary TypeToMetadataInfoTable = []; /// /// Cached creation factory for . diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeFeatureSwitches.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeFeatureSwitches.cs index d4279eeaa..c3bf6048b 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeFeatureSwitches.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeFeatureSwitches.cs @@ -33,6 +33,16 @@ internal static class WindowsRuntimeFeatureSwitches /// private const string UseWindowsUIXamlProjectionsPropertyName = "CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS"; + /// + /// The configuration property name for . + /// + private const string EnableXamlTypeMarshallingPropertyName = "CSWINRT_ENABLE_XAML_TYPE_MARSHALLING"; + + /// + /// The configuration property name for . + /// + private const string EnableIDynamicInterfaceCastableSupportPropertyName = "CSWINRT_ENABLE_IDYNAMICINTERFACECASTABLE_SUPPORT"; + /// /// Gets a value indicating whether or not manifest free WinRT activation is supported (defaults to ). /// @@ -51,6 +61,18 @@ internal static class WindowsRuntimeFeatureSwitches [FeatureSwitchDefinition(UseWindowsUIXamlProjectionsPropertyName)] public static bool UseWindowsUIXamlProjections { get; } = GetConfigurationValue(UseWindowsUIXamlProjectionsPropertyName, defaultValue: false); + /// + /// Gets a value indicating whether marshalling instances is supported. + /// + [FeatureSwitchDefinition(EnableXamlTypeMarshallingPropertyName)] + public static bool EnableXamlTypeMarshalling { get; } = GetConfigurationValue(EnableXamlTypeMarshallingPropertyName, defaultValue: true); + + /// + /// Gets a value indicating whether or not should be supported by RCW types (defaults to ). + /// + [FeatureSwitchDefinition(EnableIDynamicInterfaceCastableSupportPropertyName)] + public static bool EnableIDynamicInterfaceCastableSupport { get; } = GetConfigurationValue(EnableIDynamicInterfaceCastableSupportPropertyName, defaultValue: true); + /// /// Gets a configuration value for a specified property. /// diff --git a/src/WinRT.Runtime2/WindowsRuntimeObject.cs b/src/WinRT.Runtime2/WindowsRuntimeObject.cs index d5e115baa..405853c79 100644 --- a/src/WinRT.Runtime2/WindowsRuntimeObject.cs +++ b/src/WinRT.Runtime2/WindowsRuntimeObject.cs @@ -515,6 +515,12 @@ internal bool TryGetObjectReferenceForIEnumerableInterfaceInstance([NotNullWhen( /// RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType) { + // Fail immediately if the feature switch is disabled, to ensure all related code can be trimmed + if (!WindowsRuntimeFeatureSwitches.EnableIDynamicInterfaceCastableSupport) + { + WindowsRuntimeObjectExceptions.ThrowNotSupportedException(); + } + Type type = Type.GetTypeFromHandle(interfaceType)!; // If we can resolve the implementation type through the Windows Runtime infrastructure, return it @@ -538,6 +544,20 @@ RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTy /// bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) { + // Early feature switch check to improve trimming (same as above) + if (!WindowsRuntimeFeatureSwitches.EnableIDynamicInterfaceCastableSupport) + { + // If we should throw, explicitly throw the same exception as from 'GetInterfaceImplementation', rather than + // just returning 'false' and letting the runtime throw an 'InvalidCastException'. This allows developers to + // more easily understand why a given runtime cast might be failing under different configurations. + if (throwIfNotImplemented) + { + WindowsRuntimeObjectExceptions.ThrowNotSupportedException(); + } + + return false; + } + return TryGetCastResult( interfaceType: interfaceType, implementationType: out _, @@ -676,6 +696,13 @@ private bool LookupDynamicInterfaceCastableImplementationInfo(RuntimeTypeHandle { castResult = null; + // Also fail from here if the feature switch is disabled, to ensure that 'DynamicInterfaceCastableImplementationInfo' + // can fully be trimmed. In theory this path shouldn't be reachable if the feature is disabled, but this can help. + if (!WindowsRuntimeFeatureSwitches.EnableIDynamicInterfaceCastableSupport) + { + return false; + } + Type type = Type.GetTypeFromHandle(interfaceType)!; // If we can't resolve the implementation info at all, the cast can't possibly succeed @@ -874,4 +901,22 @@ private sealed class DynamicInterfaceCastableResult /// A dummy type to use for caching adaptive object references in . /// private static class IEnumerableInstance; +} + +/// +/// Exception stubs for . +/// +file static class WindowsRuntimeObjectExceptions +{ + /// + /// Throws a if support for is disabled. + /// + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowNotSupportedException() + { + throw new NotSupportedException( + $"Support for 'IDynamicInterfaceCastable' is disabled (make sure that the 'CsWinRTEnableIDynamicInterfaceCastableSupport' property is not set to 'false'). " + + $"In this configuration, runtime casts on Windows Runtime objects will only work if the managed object implements the target interface in metadata."); + } } \ No newline at end of file