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

Fix issue with ABI delegates in collections for .NET Standard projection #1330

Merged
merged 11 commits into from
Jun 8, 2023
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()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We should improve the naming of these tests in the future

{
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