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