Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support explicitly implementing 'ICustomPropertyProvider' #1841

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,9 @@
name="AuthoringTest.TypeOnlyActivatableViaItsOwnFactory"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
<activatableClass
name="AuthoringTest.CustomPropertyProviderWithExplicitImplementation"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
</file>
</assembly>
1 change: 1 addition & 0 deletions src/Tests/AuthoringConsumptionTest/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma pop_macro("X86")

#include <winrt/Microsoft.UI.Xaml.h>
#include <winrt/Microsoft.UI.Xaml.Data.h>
#include <winrt/Microsoft.UI.Xaml.Input.h>
#include <winrt/Microsoft.UI.Xaml.Interop.h>
#include <winrt/Microsoft.UI.Xaml.Markup.h>
Expand Down
26 changes: 26 additions & 0 deletions src/Tests/AuthoringConsumptionTest/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Microsoft::UI::Xaml::Data::ICustomPropertyProvider>();

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<hstring>(propertyValue), L"TestPropertyValue");
}
65 changes: 65 additions & 0 deletions src/Tests/AuthoringTest/Program.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);

/// <inheritdoc />
public object GetIndexedValue(object target, object index)
{
throw new NotSupportedException();
}

/// <inheritdoc />
public object GetValue(object target)
{
return "TestPropertyValue";
}

/// <inheritdoc />
public void SetIndexedValue(object target, object value, object index)
{
throw new NotSupportedException();
}

/// <inheritdoc />
public void SetValue(object target, object value)
{
throw new NotSupportedException();
}
}

[Version(3u)]
public interface IDouble
{
Expand Down
55 changes: 44 additions & 11 deletions src/WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ public static void RegisterHelperType(
internal static List<ComInterfaceEntry> GetInterfaceTableEntries(Type type)
{
var entries = new List<ComInterfaceEntry>();
bool hasCustomIMarshalInterface = false;
bool hasUserImplementedIMarshalInterface = false;
bool hasUserImplementedICustomPropertyProviderInterface = false;
bool hasWinrtExposedClassAttribute = false;

#if NET
Expand Down Expand Up @@ -229,7 +230,23 @@ static bool GetHasCustomIMarshalInterface(List<ComInterfaceEntry> 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<ComInterfaceEntry> 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))
Expand Down Expand Up @@ -260,7 +277,11 @@ static bool GetHasCustomIMarshalInterface(List<ComInterfaceEntry> entries)
else if (RuntimeFeature.IsDynamicCodeCompiled)
#endif
{
static void AddInterfaceToVtable(Type iface, List<ComInterfaceEntry> entries, bool hasCustomIMarshalInterface)
static void AddInterfaceToVtable(
Type iface,
List<ComInterfaceEntry> entries,
ref bool hasUserImplementedIMarshalInterface,
ref bool hasUserImplementedICustomPropertyProviderInterface)
{
var interfaceHelperType = iface.FindHelperType();
Guid iid = GuidGenerator.GetIID(interfaceHelperType);
Expand All @@ -270,9 +291,14 @@ static void AddInterfaceToVtable(Type iface, List<ComInterfaceEntry> 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;
}
}

Expand All @@ -283,7 +309,11 @@ static void AddInterfaceToVtable(Type iface, List<ComInterfaceEntry> 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<ComInterfaceEntry> entries, bool hasCustomIMarshalInterface)
static void GetInterfaceTableEntriesForJitEnvironment(
Type type,
List<ComInterfaceEntry> entries,
ref bool hasUserImplementedIMarshalInterface,
ref bool hasUserImplementedICustomPropertyProviderInterface)
{
if (type.IsDelegate())
{
Expand Down Expand Up @@ -312,7 +342,7 @@ static void GetInterfaceTableEntriesForJitEnvironment(Type type, List<ComInterfa
{
if (Projections.IsTypeWindowsRuntimeType(iface))
{
AddInterfaceToVtable(iface, entries, hasCustomIMarshalInterface);
AddInterfaceToVtable(iface, entries, ref hasUserImplementedIMarshalInterface, ref hasUserImplementedICustomPropertyProviderInterface);
}

if (iface.IsConstructedGenericType
Expand All @@ -322,14 +352,14 @@ static void GetInterfaceTableEntriesForJitEnvironment(Type type, List<ComInterfa
{
foreach (var compatibleIface in compatibleIfaces)
{
AddInterfaceToVtable(compatibleIface, entries, hasCustomIMarshalInterface);
AddInterfaceToVtable(compatibleIface, entries, ref hasUserImplementedIMarshalInterface, ref hasUserImplementedICustomPropertyProviderInterface);
}
}
}
}
}

GetInterfaceTableEntriesForJitEnvironment(type, entries, hasCustomIMarshalInterface);
GetInterfaceTableEntriesForJitEnvironment(type, entries, ref hasUserImplementedIMarshalInterface, ref hasUserImplementedICustomPropertyProviderInterface);
}

#if !NET
Expand Down Expand Up @@ -383,7 +413,10 @@ static void GetInterfaceTableEntriesForJitEnvironment(Type type, List<ComInterfa
Vtable = ManagedIStringableVftbl.AbiToProjectionVftablePtr
});

if (FeatureSwitches.EnableICustomPropertyProviderSupport)
// There are two scenarios where we want to support 'ICustomPropertyProvider':
// - The user is explicitly implementing the interface on their type
// - The user is using '[GeneratedBindableCustomProperty]', which uses our internal CCW
if (FeatureSwitches.EnableICustomPropertyProviderSupport && !hasUserImplementedICustomPropertyProviderInterface)
{
entries.Add(new ComInterfaceEntry
{
Expand All @@ -400,7 +433,7 @@ static void GetInterfaceTableEntriesForJitEnvironment(Type type, List<ComInterfa

// Add IMarhal implemented using the free threaded marshaler
// to all CCWs if it doesn't already have its own.
if (!hasCustomIMarshalInterface)
if (!hasUserImplementedIMarshalInterface)
{
entries.Add(new ComInterfaceEntry
{
Expand Down