diff --git a/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.exe.manifest b/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.exe.manifest
index bf43d8acd..ce6faf775 100644
--- a/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.exe.manifest
+++ b/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.exe.manifest
@@ -98,5 +98,9 @@
name="AuthoringTest.TypeOnlyActivatableViaItsOwnFactory"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
+
diff --git a/src/Tests/AuthoringConsumptionTest/pch.h b/src/Tests/AuthoringConsumptionTest/pch.h
index 92295c74b..00ed7367d 100644
--- a/src/Tests/AuthoringConsumptionTest/pch.h
+++ b/src/Tests/AuthoringConsumptionTest/pch.h
@@ -17,6 +17,7 @@
#pragma pop_macro("X86")
#include
+#include
#include
#include
#include
diff --git a/src/Tests/AuthoringConsumptionTest/test.cpp b/src/Tests/AuthoringConsumptionTest/test.cpp
index a7c5db33a..cffdc9a8b 100644
--- a/src/Tests/AuthoringConsumptionTest/test.cpp
+++ b/src/Tests/AuthoringConsumptionTest/test.cpp
@@ -745,4 +745,30 @@ TEST(AuthoringTest, NonActivatableFactory)
TEST(AuthoringTest, TypeOnlyActivatableViaItsOwnFactory)
{
EXPECT_EQ(TypeOnlyActivatableViaItsOwnFactory::Create().GetText(), L"Hello!");
+}
+
+TEST(AuthoringTest, ExplicitlyImplementedICustomPropertyProvider)
+{
+ CustomPropertyProviderWithExplicitImplementation userObject;
+
+ // We should be able to cast to 'ICustomPropertyProvider'
+ auto propertyProvider = userObject.as();
+
+ auto providerType = propertyProvider.Type();
+ EXPECT_EQ(providerType.Kind, Windows::UI::Xaml::Interop::TypeKind::Metadata);
+ EXPECT_EQ(providerType.Name, L"AuthoringTest.CustomPropertyProviderWithExplicitImplementation");
+
+ auto customProperty = propertyProvider.GetCustomProperty(L"TestCustomProperty");
+
+ EXPECT_NE(customProperty, nullptr);
+ EXPECT_TRUE(customProperty.CanRead());
+ EXPECT_FALSE(customProperty.CanWrite());
+ EXPECT_EQ(customProperty.Name(), L"TestCustomProperty");
+
+ auto propertyType = customProperty.Type();
+ EXPECT_EQ(propertyType.Kind, Windows::UI::Xaml::Interop::TypeKind::Metadata);
+ EXPECT_EQ(propertyType.Name, L"AuthoringTest.CustomPropertyWithExplicitImplementation");
+
+ auto propertyValue = customProperty.GetValue(nullptr);
+ EXPECT_EQ(winrt::unbox_value(propertyValue), L"TestPropertyValue");
}
\ No newline at end of file
diff --git a/src/Tests/AuthoringTest/Program.cs b/src/Tests/AuthoringTest/Program.cs
index db0eb5963..183d65228 100644
--- a/src/Tests/AuthoringTest/Program.cs
+++ b/src/Tests/AuthoringTest/Program.cs
@@ -1,5 +1,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Markup;
using System;
using System.Collections;
@@ -206,6 +207,70 @@ public sealed partial class CustomProperty
public string Value => "CsWinRT";
}
+ public sealed partial class CustomPropertyProviderWithExplicitImplementation : ICustomPropertyProvider
+ {
+ public Type Type => typeof(CustomPropertyProviderWithExplicitImplementation);
+
+ public ICustomProperty GetCustomProperty(string name)
+ {
+ if (name == "TestCustomProperty")
+ {
+ return new CustomPropertyWithExplicitImplementation();
+ }
+
+ return null;
+ }
+
+ public ICustomProperty GetIndexedProperty(string name, Type type)
+ {
+ return null;
+ }
+
+ public string GetStringRepresentation()
+ {
+ return string.Empty;
+ }
+ }
+
+ public sealed partial class CustomPropertyWithExplicitImplementation : ICustomProperty
+ {
+ internal CustomPropertyWithExplicitImplementation()
+ {
+ }
+
+ public bool CanRead => true;
+
+ public bool CanWrite => false;
+
+ public string Name => "TestCustomProperty";
+
+ public Type Type => typeof(CustomPropertyWithExplicitImplementation);
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetValue(object target)
+ {
+ return "TestPropertyValue";
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+ }
+
[Version(3u)]
public interface IDouble
{
diff --git a/src/WinRT.Runtime/ComWrappersSupport.cs b/src/WinRT.Runtime/ComWrappersSupport.cs
index fec935337..7ea4e9029 100644
--- a/src/WinRT.Runtime/ComWrappersSupport.cs
+++ b/src/WinRT.Runtime/ComWrappersSupport.cs
@@ -189,7 +189,8 @@ public static void RegisterHelperType(
internal static List GetInterfaceTableEntries(Type type)
{
var entries = new List();
- bool hasCustomIMarshalInterface = false;
+ bool hasUserImplementedIMarshalInterface = false;
+ bool hasUserImplementedICustomPropertyProviderInterface = false;
bool hasWinrtExposedClassAttribute = false;
#if NET
@@ -229,7 +230,23 @@ static bool GetHasCustomIMarshalInterface(List entries)
return false;
}
- hasCustomIMarshalInterface = GetHasCustomIMarshalInterface(entries);
+ // Same as above, for 'ICustomPropertyProvider' (separate method for a small perf boost).
+ // The method is very tiny, so the code duplication is not really a concern here.
+ static bool GetHasICustomPropertyProviderInterface(List entries)
+ {
+ foreach (ref readonly ComInterfaceEntry entry in CollectionsMarshal.AsSpan(entries))
+ {
+ if (entry.IID == IID.IID_ICustomPropertyProvider)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ hasUserImplementedIMarshalInterface = GetHasCustomIMarshalInterface(entries);
+ hasUserImplementedICustomPropertyProviderInterface = GetHasICustomPropertyProviderInterface(entries);
}
}
else if (type == typeof(global::System.EventHandler))
@@ -260,7 +277,11 @@ static bool GetHasCustomIMarshalInterface(List entries)
else if (RuntimeFeature.IsDynamicCodeCompiled)
#endif
{
- static void AddInterfaceToVtable(Type iface, List entries, bool hasCustomIMarshalInterface)
+ static void AddInterfaceToVtable(
+ Type iface,
+ List entries,
+ ref bool hasUserImplementedIMarshalInterface,
+ ref bool hasUserImplementedICustomPropertyProviderInterface)
{
var interfaceHelperType = iface.FindHelperType();
Guid iid = GuidGenerator.GetIID(interfaceHelperType);
@@ -270,9 +291,14 @@ static void AddInterfaceToVtable(Type iface, List entries, bo
Vtable = interfaceHelperType.GetAbiToProjectionVftblPtr()
});
- if (!hasCustomIMarshalInterface && iid == IID.IID_IMarshal)
+ if (!hasUserImplementedIMarshalInterface && iid == IID.IID_IMarshal)
+ {
+ hasUserImplementedIMarshalInterface = true;
+ }
+
+ if (!hasUserImplementedICustomPropertyProviderInterface && iid == IID.IID_ICustomPropertyProvider)
{
- hasCustomIMarshalInterface = true;
+ hasUserImplementedICustomPropertyProviderInterface = true;
}
}
@@ -283,7 +309,11 @@ static void AddInterfaceToVtable(Type iface, List entries, bo
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Fallback method for JIT environments that is not trim-safe by design.")]
[MethodImpl(MethodImplOptions.NoInlining)]
#endif
- static void GetInterfaceTableEntriesForJitEnvironment(Type type, List entries, bool hasCustomIMarshalInterface)
+ static void GetInterfaceTableEntriesForJitEnvironment(
+ Type type,
+ List entries,
+ ref bool hasUserImplementedIMarshalInterface,
+ ref bool hasUserImplementedICustomPropertyProviderInterface)
{
if (type.IsDelegate())
{
@@ -312,7 +342,7 @@ static void GetInterfaceTableEntriesForJitEnvironment(Type type, List