Skip to content

Commit

Permalink
Fix issue with ABI delegates in collections for .NET Standard project…
Browse files Browse the repository at this point in the history
…ion (#1330)

* Most of the changes to remove use of Expression.GetDelegateType for .NET standard projections

* Fix build

* Address issues to get test passing

* Fix tests

* Rearranage which files the delegates are generated in and scoping it to .NET Standard

* Add tests

* Enable test for Net Framework

* Scope delegates to NET Standard

* Update UnitTestEmbedded.csproj

* Handle edge case of generic type referencing another generic type.

* Minor clarifications
  • Loading branch information
manodasanW committed Jun 8, 2023
1 parent e644f9c commit 0afca0f
Show file tree
Hide file tree
Showing 21 changed files with 817 additions and 61 deletions.
20 changes: 20 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,24 @@

namespace winrt::Alpha::implementation
{
template <typename T, typename Allocator = std::allocator<T>>
Windows::Foundation::Collections::IVectorView<T> single_threaded_vector_view(std::vector<T, Allocator>&& values = {})
{
return make<impl::input_vector_view<T, std::vector<T, Allocator>>>(std::move(values));
}

winrt::Windows::Foundation::Collections::IVector<hstring> Class::GetStringList()
{
return winrt::single_threaded_vector<hstring>({ L"alpha", L"beta" });
}

winrt::Windows::Foundation::Collections::IVector<int> Class::GetIntList()
{
return winrt::single_threaded_vector<int>({ 4, 3, 2, 1 });
}

winrt::Windows::Foundation::Collections::IVectorView<winrt::Alpha::Class> Class::GetObjectList()
{
return single_threaded_vector_view<winrt::Alpha::Class>({ *this, *this });
}
}
4 changes: 4 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ namespace winrt::Alpha::implementation
struct Class : ClassT<Class>
{
Class() = default;

winrt::Windows::Foundation::Collections::IVector<hstring> GetStringList();
winrt::Windows::Foundation::Collections::IVector<int> GetIntList();
winrt::Windows::Foundation::Collections::IVectorView<winrt::Alpha::Class> GetObjectList();
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/Samples/TestEmbedded/C++ Components/Alpha/Alpha.idl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ namespace Alpha
runtimeclass Class
{
Class();

Windows.Foundation.Collections.IVector<String> GetStringList();
Windows.Foundation.Collections.IVector<Int32> GetIntList();
Windows.Foundation.Collections.IVectorView<Class> GetObjectList();
}

interface IAlpha
Expand Down
42 changes: 42 additions & 0 deletions src/Samples/TestEmbedded/TestEmbeddedLibrary/TestLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,48 @@ public int Test5()
{
return (int)abuff.Capacity;
}
}

public int Test6()
{
Alpha.Class a = new();

int success = 0;
var stringList = a.GetStringList();
if (stringList.Count == 2)
{
success++;
}

if (stringList[0] == "alpha" && stringList[1] == "beta")
{
success++;
}

var intList = a.GetIntList();
if (intList.Count == 4)
{
success++;
}

int sum = 0;
foreach (var i in intList)
{
sum += i;
}

if (sum == 10)
{
success++;
}

var objList = a.GetObjectList();
if ((objList[0] == objList[1]))
{
success++;
}

return success;
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/Samples/TestEmbedded/UnitTestEmbedded/TestClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public void Test4()
public void Test5()
{
Assert.Equal(20, TestLib.Test5());
}

[Fact]
public void Test6()
{
Assert.Equal(5, TestLib.Test6());
}
}
}
40 changes: 40 additions & 0 deletions src/Tests/UnitTest/TestModuleInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using WinRT;

#if !NET

namespace UnitTest
{
// In our .NET standard support, we generate most of the delegates needed by generic types as part of the projection. But for the ones
// passed or obtained as an Object, we are not able to statically detect that. On .NET Core, we are able to utilize Expression.GetDelegateType
// to dynamically create one in this case, but on .NET Framework we are not able to do that for ones with pointers in their parameters.
// This tests that scenario where the ABI delegates need to be manually declared and registered by the caller.
internal static class ProjectionTypesInitializer
{
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void InitalizeProjectionTypes()
{
Projections.RegisterAbiDelegate(new Type[] { typeof(void*), typeof(ABI.TestComponent.NonBlittable).MakeByRefType(), typeof(int) }, typeof(_get_Value_NonBlittable));
Projections.RegisterAbiDelegate(new Type[] { typeof(void*), typeof(TestComponentCSharp.EnumValue).MakeByRefType(), typeof(int) }, typeof(_get_Value_EnumValue));
Projections.RegisterAbiDelegate(new Type[] { typeof(void*), typeof(uint), typeof(ABI.TestComponent.Composable).MakeByRefType(), typeof(int) }, typeof(_get_at_Composable));
Projections.RegisterAbiDelegate(new Type[] { typeof(void*), typeof(ABI.TestComponent.Composable), typeof(uint).MakeByRefType(), typeof(byte).MakeByRefType(), typeof(int) }, typeof(_index_of_Composable));
Projections.RegisterAbiDelegate(new Type[] { typeof(void*), typeof(uint), typeof(ABI.TestComponent.Composable), typeof(int) }, typeof(_set_at_Composable));
Projections.RegisterAbiDelegate(new Type[] { typeof(void*), typeof(ABI.TestComponent.Composable), typeof(int) }, typeof(_append_Composable));
}

internal unsafe delegate int _get_Value_NonBlittable(void* thisPtr, out ABI.TestComponent.NonBlittable __return_value__);
internal unsafe delegate int _get_Value_EnumValue(void* thisPtr, out TestComponentCSharp.EnumValue __return_value__);
internal unsafe delegate int _get_at_Composable(void* thisPtr, uint index, out ABI.TestComponent.Composable __return_value__);
internal unsafe delegate int _index_of_Composable(void* thisPtr, ABI.TestComponent.Composable value, out uint index, out byte found);
internal unsafe delegate int _set_at_Composable(void* thisPtr, uint index, ABI.TestComponent.Composable value);
internal unsafe delegate int _append_Composable(void* thisPtr, ABI.TestComponent.Composable value);
}
}

namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Method)]
internal sealed class ModuleInitializerAttribute : Attribute { }
}

#endif
26 changes: 12 additions & 14 deletions src/WinRT.Runtime/FundamentalMarshalers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,26 @@ namespace ABI.System
{
internal struct Boolean
{
byte value;
public static bool CreateMarshaler(bool value) => value;
public static Boolean GetAbi(bool value) => new Boolean() { value = (byte)(value ? 1 : 0) };
public static bool FromAbi(Boolean abi) => abi.value != 0;
public static unsafe void CopyAbi(bool value, IntPtr dest) => *(byte*)dest.ToPointer() = GetAbi(value).value;
public static Boolean FromManaged(bool value) => GetAbi(value);
public static unsafe void CopyManaged(bool arg, IntPtr dest) => *(byte*)dest.ToPointer() = FromManaged(arg).value;
public static byte GetAbi(bool value) => (byte)(value ? 1 : 0);
public static bool FromAbi(byte abi) => abi != 0;
public static unsafe void CopyAbi(bool value, IntPtr dest) => *(byte*)dest.ToPointer() = GetAbi(value);
public static byte FromManaged(bool value) => GetAbi(value);
public static unsafe void CopyManaged(bool arg, IntPtr dest) => *(byte*)dest.ToPointer() = FromManaged(arg);
public static void DisposeMarshaler(bool m) { }
public static void DisposeAbi(Boolean abi) { }
public static void DisposeAbi(byte abi) { }
}

internal struct Char
{
ushort value;
public static char CreateMarshaler(char value) => value;
public static Char GetAbi(char value) => new Char() { value = (ushort)value };
public static char FromAbi(Char abi) => (char)abi.value;
public static unsafe void CopyAbi(char value, IntPtr dest) => *(ushort*)dest.ToPointer() = GetAbi(value).value;
public static Char FromManaged(char value) => GetAbi(value);
public static unsafe void CopyManaged(char arg, IntPtr dest) => *(ushort*)dest.ToPointer() = FromManaged(arg).value;
public static ushort GetAbi(char value) => (ushort)value;
public static char FromAbi(ushort abi) => (char)abi;
public static unsafe void CopyAbi(char value, IntPtr dest) => *(ushort*)dest.ToPointer() = GetAbi(value);
public static ushort FromManaged(char value) => GetAbi(value);
public static unsafe void CopyManaged(char arg, IntPtr dest) => *(ushort*)dest.ToPointer() = FromManaged(arg);
public static void DisposeMarshaler(char m) { }
public static void DisposeAbi(Char abi) { }
public static void DisposeAbi(ushort abi) { }
}
}

32 changes: 31 additions & 1 deletion src/WinRT.Runtime/Interop/StandardDelegates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,34 @@ namespace WinRT.Interop
public
#endif
delegate int _remove_EventHandler(IntPtr thisPtr, EventRegistrationToken token);
}

#if !NET
internal unsafe delegate int _get_Current_IntPtr(void* thisPtr, out IntPtr __return_value__);
internal unsafe delegate int _get_Current_Type(void* thisPtr, out ABI.System.Type __return_value__);

internal unsafe delegate int _get_At_IntPtr(void* thisPtr, uint index, out IntPtr __return_value__);
internal unsafe delegate int _get_At_Type(void* thisPtr, uint index, out ABI.System.Type __return_value__);
internal unsafe delegate int _index_Of_IntPtr(void* thisPtr, IntPtr value, out uint index, out byte found);
internal unsafe delegate int _index_Of_Type(void* thisPtr, ABI.System.Type value, out uint index, out byte found);
internal unsafe delegate int _set_At_IntPtr(void* thisPtr, uint index, IntPtr value);
internal unsafe delegate int _set_At_Type(void* thisPtr, uint index, ABI.System.Type value);
internal unsafe delegate int _append_IntPtr(void* thisPtr, IntPtr value);
internal unsafe delegate int _append_Type(void* thisPtr, ABI.System.Type value);

internal unsafe delegate int _lookup_IntPtr_IntPtr(void* thisPtr, IntPtr key, out IntPtr value);
internal unsafe delegate int _lookup_Type_Type(void* thisPtr, ABI.System.Type key, out ABI.System.Type value);
internal unsafe delegate int _lookup_IntPtr_Type(void* thisPtr, IntPtr key, out ABI.System.Type value);
internal unsafe delegate int _lookup_Type_IntPtr(void* thisPtr, ABI.System.Type key, out IntPtr value);
internal unsafe delegate int _has_key_IntPtr(void* thisPtr, IntPtr key, out byte found);
internal unsafe delegate int _has_key_Type(void* thisPtr, ABI.System.Type key, out byte found);
internal unsafe delegate int _insert_IntPtr_IntPtr(void* thisPtr, IntPtr key, IntPtr value, out byte replaced);
internal unsafe delegate int _insert_Type_Type(void* thisPtr, ABI.System.Type key, ABI.System.Type value, out byte replaced);
internal unsafe delegate int _insert_IntPtr_Type(void* thisPtr, IntPtr key, ABI.System.Type value, out byte replaced);
internal unsafe delegate int _insert_Type_IntPtr(void* thisPtr, ABI.System.Type key, IntPtr value, out byte replaced);

internal unsafe delegate int _invoke_IntPtr_IntPtr(void* thisPtr, IntPtr sender, IntPtr args);
internal unsafe delegate int _invoke_IntPtr_Type(void* thisPtr, IntPtr sender, ABI.System.Type args);
internal unsafe delegate int _invoke_Type_IntPtr(void* thisPtr, ABI.System.Type sender, IntPtr args);
internal unsafe delegate int _invoke_Type_Type(void* thisPtr, ABI.System.Type sender, ABI.System.Type args);
#endif
}
21 changes: 16 additions & 5 deletions src/WinRT.Runtime/Marshalers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,13 +1519,24 @@ static Marshaler()
}
else if (type.IsValueType)
{
AbiType = type.FindHelperType();
if (AbiType != null)
if (type == typeof(bool))
{
// Could still be blittable and the 'ABI.*' type exists for other reasons (e.g. it's a mapped type)
if (AbiType.GetMethod("FromAbi", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) == null)
AbiType = typeof(byte);
}
else if (type == typeof(char))
{
AbiType = typeof(ushort);
}
else
{
AbiType = type.FindHelperType();
if (AbiType != null)
{
AbiType = null;
// Could still be blittable and the 'ABI.*' type exists for other reasons (e.g. it's a mapped type)
if (AbiType.GetMethod("FromAbi", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) == null)
{
AbiType = null;
}
}
}

Expand Down
75 changes: 75 additions & 0 deletions src/WinRT.Runtime/Projections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Numerics;
using System.Reflection;
using System.Threading;
Expand Down Expand Up @@ -541,5 +542,79 @@ internal static bool TryGetMarshalerTypeForProjectedRuntimeClass<T>(IObjectRefer
type = null;
return false;
}

#if NET
internal static Type GetAbiDelegateType(params Type[] typeArgs) => Expression.GetDelegateType(typeArgs);
#else
private class DelegateTypeComparer : IEqualityComparer<Type[]>
{
public bool Equals(Type[] x, Type[] y)
{
return x.SequenceEqual(y);
}

public int GetHashCode(Type[] obj)
{
int hashCode = 0;
for (int idx = 0; idx < obj.Length; idx++)
{
hashCode ^= obj[idx].GetHashCode();
}
return hashCode;
}
}

private static readonly ConcurrentDictionary<Type[], Type> abiDelegateCache = new(new DelegateTypeComparer())
{
// IEnumerable
[new Type[] { typeof(void*), typeof(IntPtr).MakeByRefType(), typeof(int) }] = typeof(Interop._get_Current_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type).MakeByRefType(), typeof(int) }] = typeof(Interop._get_Current_Type),
// IList / IReadOnlyList
[new Type[] { typeof(void*), typeof(uint), typeof(IntPtr).MakeByRefType(), typeof(int) }] = typeof(Interop._get_At_IntPtr),
[new Type[] { typeof(void*), typeof(uint), typeof(ABI.System.Type).MakeByRefType(), typeof(int) }] = typeof(Interop._get_At_Type),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(uint).MakeByRefType(), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._index_Of_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(uint).MakeByRefType(), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._index_Of_Type),
[new Type[] { typeof(void*), typeof(uint), typeof(IntPtr), typeof(int) }] = typeof(Interop._set_At_IntPtr),
[new Type[] { typeof(void*), typeof(uint), typeof(ABI.System.Type), typeof(int) }] = typeof(Interop._set_At_Type),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(int) }] = typeof(Interop._append_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(int) }] = typeof(Interop._append_Type),
// IDictionary / IReadOnlyDictionary
[new Type[] { typeof(void*), typeof(IntPtr), typeof(IntPtr).MakeByRefType(), typeof(int) }] = typeof(Interop._lookup_IntPtr_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(ABI.System.Type).MakeByRefType(), typeof(int) }] = typeof(Interop._lookup_Type_Type),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(ABI.System.Type).MakeByRefType(), typeof(int) }] = typeof(Interop._lookup_IntPtr_Type),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(IntPtr).MakeByRefType(), typeof(int) }] = typeof(Interop._lookup_Type_IntPtr),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._has_key_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._has_key_Type),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(IntPtr), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._insert_IntPtr_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(ABI.System.Type), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._insert_Type_Type),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(ABI.System.Type), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._insert_IntPtr_Type),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(IntPtr), typeof(byte).MakeByRefType(), typeof(int) }] = typeof(Interop._insert_Type_IntPtr),
// EventHandler
[new Type[] { typeof(void*), typeof(IntPtr), typeof(IntPtr), typeof(int) }] = typeof(Interop._invoke_IntPtr_IntPtr),
[new Type[] { typeof(void*), typeof(IntPtr), typeof(ABI.System.Type), typeof(int) }] = typeof(Interop._invoke_IntPtr_Type),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(IntPtr), typeof(int) }] = typeof(Interop._invoke_Type_IntPtr),
[new Type[] { typeof(void*), typeof(ABI.System.Type), typeof(ABI.System.Type), typeof(int) }] = typeof(Interop._invoke_Type_Type),
};

public static void RegisterAbiDelegate(Type[] delegateSignature, Type delegateType)
{
abiDelegateCache.TryAdd(delegateSignature, delegateType);
}

// The .NET Standard projection can be used in both .NET Core and .NET Framework scenarios.
// With the latter, using Expression.GetDelegateType to create custom delegates with void* parameters
// doesn't seem to be supported. So we handle that by pregenerating all the ABI delegates that we need
// based on the WinMD and also by allowing apps to register their own if there are any
// that we couldn't detect (i.e. types passed as object in WinMD).
public static Type GetAbiDelegateType(params Type[] typeArgs)
{
if (abiDelegateCache.TryGetValue(typeArgs, out var delegateType))
{
return delegateType;
}

return Expression.GetDelegateType(typeArgs);
}
#endif
}
}
2 changes: 1 addition & 1 deletion src/WinRT.Runtime/Projections/EventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace ABI.System
static class EventHandler<T>
{
public static Guid PIID = GuidGenerator.CreateIID(typeof(global::System.EventHandler<T>));
private static readonly global::System.Type Abi_Invoke_Type = Expression.GetDelegateType(new global::System.Type[] { typeof(void*), typeof(IntPtr), Marshaler<T>.AbiType, typeof(int) });
private static readonly global::System.Type Abi_Invoke_Type = Projections.GetAbiDelegateType(new global::System.Type[] { typeof(void*), typeof(IntPtr), Marshaler<T>.AbiType, typeof(int) });

private static readonly global::WinRT.Interop.IDelegateVftbl AbiToProjectionVftable;
public static readonly IntPtr AbiToProjectionVftablePtr;
Expand Down
Loading

0 comments on commit 0afca0f

Please sign in to comment.