Skip to content

Commit

Permalink
Fix nullable and IPropertySet scenarios (#1519)
Browse files Browse the repository at this point in the history
* Experiment with changes to avoid nullable.value going down the helper type route

* Fix build

* Fix nullable structs and delegates

* Fix

* Fix tests

* Fix issue where the class implementing the interface can be trimmed and we rely on IDIC cast to create an instance of the interface.  But that doesn't work with the interface inherits generic interfaces on AOT.  This addresses that by making sure there is a fallback class available to use for the interface.

* Fix warning

* Improvements to nullable scenario (caching and removing old code paths)

* Move where we do generic type initialization for derived generic interfaces impl class

* Fix issue with Interface<> missing in impl classes by converting most of the remaining ones using it to the new format

* Add test

* PR feedback

* PR feedback for IID
  • Loading branch information
manodasanW authored and Sergio0694 committed Mar 12, 2024
1 parent 14e6ea3 commit 05d472f
Show file tree
Hide file tree
Showing 15 changed files with 746 additions and 512 deletions.
11 changes: 9 additions & 2 deletions src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Windows.Foundation;
using System;
using Windows.Foundation;

// Static function calls and create RCW for existing object.
IStringable[] a = new IStringable[] {
Expand All @@ -18,4 +19,10 @@
// Class function call
result += (int)(a[1] as Windows.Data.Json.JsonValue).GetNumber();

return result == 16 ? 100 : 101;
var enumVal = TestComponentCSharp.Class.BoxedEnum;
if (enumVal is TestComponentCSharp.EnumValue val && val == TestComponentCSharp.EnumValue.Two)
{
result += 1;
}

return result == 17 ? 100 : 101;
5 changes: 5 additions & 0 deletions src/Tests/TestComponentCSharp/TestComponentCSharp.idl
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ namespace TestComponentCSharp
static Int32 NumObjects{ get; };
}

interface IDerivedGenericInterface requires Microsoft.UI.Xaml.Input.ICommand, Microsoft.UI.Xaml.Interop.INotifyCollectionChanged, Windows.Foundation.Collections.IPropertySet
{
Int32 Number;
}

interface ISingleton
{
Int32 IntProperty;
Expand Down
68 changes: 29 additions & 39 deletions src/WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,13 +444,13 @@ private static Func<IntPtr, object> CreateDelegateFactory(Type type)

public static bool RegisterDelegateFactory(Type implementationType, Func<IntPtr, object> delegateFactory) => DelegateFactoryCache.TryAdd(implementationType, delegateFactory);

private static Func<IInspectable, object> CreateNullableTFactory(Type implementationType)
internal static Func<IInspectable, object> CreateNullableTFactory(Type implementationType)
{
var getValueMethod = implementationType.GetHelperType().GetMethod("GetValue", BindingFlags.Static | BindingFlags.Public);
return (IInspectable obj) => getValueMethod.Invoke(null, new[] { obj });
}

private static Func<IInspectable, object> CreateAbiNullableTFactory(
internal static Func<IInspectable, object> CreateAbiNullableTFactory(
#if NET
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
#endif
Expand All @@ -476,7 +476,7 @@ private static Func<IInspectable, object> CreateArrayFactory(Type implementation
// This is done to avoid pointer reuse until GC cleans up the boxed object
private static readonly ConditionalWeakTable<object, IInspectable> _boxedValueReferenceCache = new();

private static Func<IInspectable, object> CreateReferenceCachingFactory(Func<IInspectable, object> internalFactory)
internal static Func<IInspectable, object> CreateReferenceCachingFactory(Func<IInspectable, object> internalFactory)
{
return inspectable =>
{
Expand Down Expand Up @@ -513,55 +513,37 @@ internal static Func<IInspectable, object> CreateTypedRcwFactory(Type implementa
return (IInspectable obj) => obj;
}

if (implementationType == typeof(ABI.System.Nullable_string))
if (implementationType == typeof(string) ||
implementationType == typeof(Type) ||
implementationType == typeof(Exception) ||
implementationType.IsDelegate())
{
return CreateReferenceCachingFactory(ABI.System.Nullable_string.GetValue);
}
else if (implementationType == typeof(ABI.System.Nullable_Type))
{
return CreateReferenceCachingFactory(ABI.System.Nullable_Type.GetValue);
}
else if (implementationType == typeof(ABI.System.Nullable_Exception))
{
return CreateReferenceCachingFactory(ABI.System.Nullable_Exception.GetValue);
}

var customHelperType = Projections.FindCustomHelperTypeMapping(implementationType, true);
if (customHelperType != null)
{
return CreateReferenceCachingFactory(CreateCustomTypeMappingFactory(customHelperType));
}

if (implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>))
{
return CreateReferenceCachingFactory(CreateKeyValuePairFactory(implementationType));
return ABI.System.NullableType.GetValueFactory(implementationType);
}

if (implementationType.IsValueType)
{
if (implementationType.IsNullableT())
if (implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>))
{
return CreateReferenceCachingFactory(CreateKeyValuePairFactory(implementationType));
}
else if (implementationType.IsNullableT())
{
return CreateReferenceCachingFactory(CreateNullableTFactory(implementationType));
return ABI.System.NullableType.GetValueFactory(implementationType.GetGenericArguments()[0]);
}
else
{
#if NET
if (!RuntimeFeature.IsDynamicCodeCompiled)
{
throw new NotSupportedException($"Cannot create an RCW factory for implementation type '{implementationType}'.");
}
#endif

#pragma warning disable IL3050 // https://github.com/dotnet/runtime/issues/97273
return CreateReferenceCachingFactory(CreateNullableTFactory(typeof(System.Nullable<>).MakeGenericType(implementationType)));
#pragma warning restore IL3050
return ABI.System.NullableType.GetValueFactory(implementationType);
}
}
else if (implementationType.IsAbiNullableDelegate())

var customHelperType = Projections.FindCustomHelperTypeMapping(implementationType, true);
if (customHelperType != null)
{
return CreateReferenceCachingFactory(CreateAbiNullableTFactory(implementationType));
return CreateReferenceCachingFactory(CreateCustomTypeMappingFactory(customHelperType));
}
else if (implementationType.IsIReferenceArray())

if (implementationType.IsIReferenceArray())
{
return CreateReferenceCachingFactory(CreateArrayFactory(implementationType));
}
Expand All @@ -575,6 +557,14 @@ internal static Type GetRuntimeClassForTypeCreation(IInspectable inspectable, Ty
Type implementationType = null;
if (!string.IsNullOrEmpty(runtimeClassName))
{
// Check if this is a nullable type where there are no references to the nullable version, but
// there is to the actual type.
if (runtimeClassName.StartsWith("Windows.Foundation.IReference`1<", StringComparison.Ordinal))
{
// runtimeClassName is of format Windows.Foundation.IReference`1<type>.
return TypeNameSupport.FindRcwTypeByNameCached(runtimeClassName.Substring(32, runtimeClassName.Length - 33));
}

implementationType = TypeNameSupport.FindRcwTypeByNameCached(runtimeClassName);
}

Expand Down
6 changes: 4 additions & 2 deletions src/WinRT.Runtime/ComWrappersSupport.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ static partial class ComWrappersSupport
public static T CreateRcwForComObject<T>(IntPtr ptr)
{
return CreateRcwForComObject<T>(ptr, true);
}
}

internal static Func<IInspectable, object> GetTypedRcwFactory(Type implementationType) => TypedObjectFactoryCacheForType.GetOrAdd(implementationType, classType => CreateTypedRcwFactory(classType));

private static T CreateRcwForComObject<T>(IntPtr ptr, bool tryUseCache)
{
Expand All @@ -56,7 +58,7 @@ private static T CreateRcwForComObject<T>(IntPtr ptr, bool tryUseCache)
if (typeof(T).IsSealed)
{
runtimeWrapper = TypedObjectFactoryCacheForType.GetOrAdd(typeof(T), classType => CreateTypedRcwFactory(classType))(inspectable);
runtimeWrapper = GetTypedRcwFactory(typeof(T))(inspectable);
}
else
{
Expand Down
61 changes: 46 additions & 15 deletions src/WinRT.Runtime/Marshalers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1374,16 +1374,43 @@ struct MarshalInterface<T>
{
#if NET
[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.NonPublicFields |
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicMethods)]
#endif
private static readonly Type HelperType = typeof(T).GetHelperType();
private static FieldInfo _ObjField;
private static Type _HelperType;

// Either a Func<T, IObjectReference> (JIT) or a boxed Guid (NativeAOT)
private static object _CreateMarshalerOrIid;
#if NET
[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicFields |
DynamicallyAccessedMemberTypes.PublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicMethods)]
#endif
private static Type HelperType => _HelperType ??= typeof(T).GetHelperType();

private static object _CreateMarshaler;

// We are here using CreateIID on the projected type rather than GetIID on the helper type
// This allows us to avoid needing to do MakeGenericType calls or
// registration of helper types for projected generic types like System.Nullable<>.
// By using CreateIID with the projected type, we are still able to get the IID
// but at the same time don't need the generic instance of the helper type to do so.
// In addition, other than for that, we don't need the helper type in MarshalInterface.
// This does mean we are doing the PIID calculation here instead of using the cached
// one in the helper type, but we are also caching it here too so it should be only
// one additional call.
private static object _Iid;
private static Guid IID => (Guid)(_Iid ??= GetIID());

private static Guid GetIID()
{
if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>))
{
return GuidGenerator.CreateIID(typeof(T));
}

return GuidGenerator.GetIID(HelperType);
}

public static T FromAbi(IntPtr ptr)
{
Expand All @@ -1402,6 +1429,7 @@ public static IObjectReference CreateMarshaler(T value)
return null;
}

#if !NET
if (TryGetObjFieldValue(value, out IObjectReference objectReference))
{
IntPtr ptr = objectReference.GetRef();
Expand All @@ -1411,6 +1439,7 @@ public static IObjectReference CreateMarshaler(T value)
// on the same thread (and as a result don't need to capture context).
return ObjectReference<IUnknownVftbl>.Attach(ref ptr);
}
#endif

return CreateMarshalerCore(value);
}
Expand All @@ -1422,12 +1451,14 @@ public static ObjectReferenceValue CreateMarshaler2(T value, Guid iid = default)
return new ObjectReferenceValue();
}

#if !NET
if (TryGetObjFieldValue(value, out IObjectReference objectReference))
{
return objectReference.AsValue();
}
#endif

return MarshalInspectable<T>.CreateMarshaler2(value, iid == default ? GuidGenerator.GetIID(HelperType) : iid, true);
return MarshalInspectable<T>.CreateMarshaler2(value, iid == default ? IID : iid, true);
}

public static IntPtr GetAbi(IObjectReference value) =>
Expand Down Expand Up @@ -1479,6 +1510,8 @@ public static unsafe void CopyManaged(T value, IntPtr dest)

public static unsafe void DisposeAbiArray(object box) => MarshalInterfaceHelper<T>.DisposeAbiArray(box);

#if !NET
private static FieldInfo _ObjField;
private static bool TryGetObjFieldValue(T value, out IObjectReference objectReference)
{
// If the value passed in is the native implementation of the interface,
Expand All @@ -1496,43 +1529,41 @@ private static bool TryGetObjFieldValue(T value, out IObjectReference objectRefe

return false;
}
#endif

private static IObjectReference CreateMarshalerCore(T value)
{
#if NET
// On NativeAOT, we can inline everything and skip creating any delegates
if (!RuntimeFeature.IsDynamicCodeCompiled)
{
_CreateMarshalerOrIid ??= GuidGenerator.GetIID(HelperType);

return MarshalInspectable<T>.CreateMarshaler<IUnknownVftbl>(value, (Guid)_CreateMarshalerOrIid, true);
return MarshalInspectable<T>.CreateMarshaler<IUnknownVftbl>(value, IID, true);
}
#endif
// Otherwise, just use the fallback path
#pragma warning disable IL3050 // https://github.com/dotnet/runtime/issues/97273
_CreateMarshalerOrIid ??= BindCreateMarshaler();
_CreateMarshaler ??= BindCreateMarshaler();
#pragma warning restore IL3050

return ((Func<T, IObjectReference>)_CreateMarshalerOrIid)(value);
return ((Func<T, IObjectReference>)_CreateMarshaler)(value);
}

#if NET8_0_OR_GREATER
[RequiresDynamicCode(AttributeMessages.MarshallingOrGenericInstantiationsRequiresDynamicCode)]
#endif
private static Func<T, IObjectReference> BindCreateMarshaler()
{
Guid iid = GuidGenerator.GetIID(HelperType);
var vftblType = HelperType.FindVftblType();

if (vftblType is not null)
{
var methodInfo = typeof(MarshalInspectable<T>).GetMethod("CreateMarshaler", new Type[] { typeof(T), typeof(Guid), typeof(bool) }).
MakeGenericMethod(vftblType);
var createMarshaler = (Func<T, Guid, bool, IObjectReference>)methodInfo.CreateDelegate(typeof(Func<T, Guid, bool, IObjectReference>));
return obj => createMarshaler(obj, iid, true);
return obj => createMarshaler(obj, IID, true);
}

return obj => MarshalInspectable<T>.CreateMarshaler<IUnknownVftbl>(obj, iid, true);
return obj => MarshalInspectable<T>.CreateMarshaler<IUnknownVftbl>(obj, IID, true);
}
}

Expand Down
Loading

0 comments on commit 05d472f

Please sign in to comment.