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

Consume IDynamicInterfaceCastable in the .NET 5 projection #369

Merged
merged 26 commits into from
Sep 26, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4be3620
Don't ship the netstandard2.0 projection support.
jkoritzinsky Aug 11, 2020
7ad98c8
Start refactoring for implementing IDynamicInterfaceCastable.
jkoritzinsky Aug 12, 2020
58c590a
Add IWinRTObject interface and continue implementing the updates to i…
jkoritzinsky Aug 13, 2020
6e455f3
Implement IDynamicInterfaceCastable for generated code excluding fact…
jkoritzinsky Aug 13, 2020
686cead
Add fallback for when an interface is kept alive but its Vftbl type w…
jkoritzinsky Aug 13, 2020
4fd093b
Move statics and factory caches to the IDynamicInterfaceCastable plan.
jkoritzinsky Aug 14, 2020
89a83ee
Use the QI cache when checking if an interface is implemented.
jkoritzinsky Aug 14, 2020
6511ae6
Move activation/composition to work with IDynamicInterfaceCastable.
jkoritzinsky Aug 14, 2020
bcaf88e
Use equality functions instead of ThisPtr.
jkoritzinsky Aug 14, 2020
cc30448
Implement static interface support by using dynamic casting under the…
jkoritzinsky Aug 14, 2020
1f2efee
Update to LangVersion preview
jkoritzinsky Aug 14, 2020
1f4d869
Fix projected required interface implementations and handle default i…
jkoritzinsky Aug 14, 2020
25e4e4b
Use Spans instead of LINQ to improve perf in Guid generation (startup…
jkoritzinsky Aug 14, 2020
b58f37d
Get WinUI projection compiling with no new warnings.
jkoritzinsky Aug 14, 2020
a02a72c
Fix more collections so the Windows projection also builds.
jkoritzinsky Aug 14, 2020
b5c3c31
Cache cast results in .NET 5 projection to regain some perf losses.
jkoritzinsky Aug 17, 2020
597a024
Add support for IDynamicInterfaceCastable-based interfaces in RCW cre…
jkoritzinsky Aug 17, 2020
229fead
Merge remote-tracking branch 'origin' into IDynamicInterfaceCastable
jkoritzinsky Sep 10, 2020
c38b4a2
Add field access optimization for default interfaces
ujjwalchadha Sep 16, 2020
471979e
Added manual type mappings for IDynamicInterfaceCastable
ujjwalchadha Sep 23, 2020
ba117da
Code cleanup
ujjwalchadha Sep 24, 2020
1cb8c01
Add tests for IDynamicInterfaceCastable
ujjwalchadha Sep 24, 2020
4418c1b
Merged Master into IDynamicInterfaceCastable
ujjwalchadha Sep 25, 2020
80ccff5
Merge branch 'master' into IDynamicInterfaceCastable
ujjwalchadha Sep 25, 2020
94294d2
Fix build errors
ujjwalchadha Sep 25, 2020
b06d2d3
Fix authoring
manodasanW Sep 26, 2020
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
7 changes: 1 addition & 6 deletions Projections/WinUI/WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
<Platforms>x64;x86</Platforms>
<LangVersion>8</LangVersion>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -12,11 +12,6 @@
<NoWarn>8305;0618</NoWarn>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<ProjectReference Include="..\..\WinRT.Runtime\WinRT.Runtime.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion Projections/Windows/Windows.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
<Platforms>x64;x86</Platforms>
<LangVersion>8</LangVersion>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
5 changes: 3 additions & 2 deletions UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ public void TestClassGeneric()
{
var obj = objs[i];
Assert.Same(obj, TestObject);
Assert.Equal(TestObject.ThisPtr, objs[i].ThisPtr);
Assert.Equal(TestObject, objs[i]);
Copy link
Member

Choose a reason for hiding this comment

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

how come the ThisPtr check was removed?

Copy link
Member Author

Choose a reason for hiding this comment

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

There's no longer a ThisPtr property exposed because it makes the public surface area not match the WinMD contract and it isn't entirely accurate for composed types. There's no good reason to use the ThisPtr property, so I removed it early in the design.

}
}
[Fact]
Expand Down Expand Up @@ -1535,7 +1535,8 @@ public void WeakReferenceOfNativeObjectRehydratedAfterWrapperIsCollected()
[Fact]
public void TestUnwrapInspectable()
{
var inspectable = IInspectable.FromAbi(TestObject.ThisPtr);
using var objRef = MarshalInspectable.CreateMarshaler(TestObject);
var inspectable = IInspectable.FromAbi(objRef.ThisPtr);
Assert.True(ComWrappersSupport.TryUnwrapObject(inspectable, out _));
}

Expand Down
40 changes: 1 addition & 39 deletions WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,45 +307,7 @@ internal static Func<IInspectable, object> CreateTypedRcwFactory(string runtimeC
return CreateArrayFactory(implementationType);
}

Type classType;
Type interfaceType;
Type vftblType;
if (implementationType.IsInterface)
{
classType = null;
interfaceType = implementationType.GetHelperType() ??
throw new TypeLoadException($"Unable to find an ABI implementation for the type '{runtimeClassName}'");
vftblType = interfaceType.FindVftblType() ?? throw new TypeLoadException($"Unable to find a vtable type for the type '{runtimeClassName}'");
if (vftblType.IsGenericTypeDefinition)
{
vftblType = vftblType.MakeGenericType(interfaceType.GetGenericArguments());
}
}
else
{
classType = implementationType;
interfaceType = Projections.GetDefaultInterfaceTypeForRuntimeClassType(classType);
if (interfaceType is null)
{
throw new TypeLoadException($"Unable to create a runtime wrapper for a WinRT object of type '{runtimeClassName}'. This type is not a projected type.");
}
vftblType = interfaceType.FindVftblType() ?? throw new TypeLoadException($"Unable to find a vtable type for the type '{runtimeClassName}'");
}

ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") };
var createInterfaceInstanceExpression = Expression.New(interfaceType.GetConstructor(new[] { typeof(ObjectReference<>).MakeGenericType(vftblType) }),
Expression.Call(parms[0],
typeof(IInspectable).GetMethod(nameof(IInspectable.As)).MakeGenericMethod(vftblType)));

if (classType is null)
{
return Expression.Lambda<Func<IInspectable, object>>(createInterfaceInstanceExpression, parms).Compile();
}

return Expression.Lambda<Func<IInspectable, object>>(
Expression.New(classType.GetConstructor(BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] { interfaceType }, null),
createInterfaceInstanceExpression),
parms).Compile();
return CreateFactoryForImplementationType(runtimeClassName, implementationType);
}

private static bool ShouldProvideIReference(object obj)
Expand Down
17 changes: 17 additions & 0 deletions WinRT.Runtime/ComWrappersSupport.net5.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -97,6 +98,22 @@ public static void InitializeComWrappers(ComWrappers wrappers = null)
}

internal static Func<IInspectable, object> GetTypedRcwFactory(string runtimeClassName) => TypedObjectFactoryCache.GetOrAdd(runtimeClassName, className => CreateTypedRcwFactory(className));


private static Func<IInspectable, object> CreateFactoryForImplementationType(string runtimeClassName, Type implementationType)
{
if (implementationType.IsInterface)
{
return obj => obj;
}

ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") };

return Expression.Lambda<Func<IInspectable, object>>(
Expression.New(implementationType.GetConstructor(BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] { typeof(IObjectReference) }, null),
Expression.Property(parms[0], nameof(WinRT.IInspectable.ObjRef))),
parms).Compile();
}
}

public class DefaultComWrappers : ComWrappers
Expand Down
46 changes: 46 additions & 0 deletions WinRT.Runtime/ComWrappersSupport.netstandard2.0.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
Expand Down Expand Up @@ -181,6 +183,50 @@ public RuntimeWrapperCleanup(IntPtr identityComObject, System.WeakReference<obje
}
}
}

private static Func<IInspectable, object> CreateFactoryForImplementationType(string runtimeClassName, Type implementationType)
{
Type classType;
Type interfaceType;
Type vftblType;
if (implementationType.IsInterface)
{
classType = null;
interfaceType = implementationType.GetHelperType() ??
throw new TypeLoadException($"Unable to find an ABI implementation for the type '{runtimeClassName}'");
vftblType = interfaceType.FindVftblType() ?? throw new TypeLoadException($"Unable to find a vtable type for the type '{runtimeClassName}'");
if (vftblType.IsGenericTypeDefinition)
{
vftblType = vftblType.MakeGenericType(interfaceType.GetGenericArguments());
}
}
else
{
classType = implementationType;
interfaceType = Projections.GetDefaultInterfaceTypeForRuntimeClassType(classType);
if (interfaceType is null)
{
throw new TypeLoadException($"Unable to create a runtime wrapper for a WinRT object of type '{runtimeClassName}'. This type is not a projected type.");
}
vftblType = interfaceType.FindVftblType() ?? throw new TypeLoadException($"Unable to find a vtable type for the type '{runtimeClassName}'");
}

ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") };
var createInterfaceInstanceExpression = Expression.New(interfaceType.GetConstructor(new[] { typeof(ObjectReference<>).MakeGenericType(vftblType) }),
Expression.Call(parms[0],
typeof(IInspectable).GetMethod(nameof(IInspectable.As)).MakeGenericMethod(vftblType)));

if (classType is null)
{
return Expression.Lambda<Func<IInspectable, object>>(createInterfaceInstanceExpression, parms).Compile();
}

return Expression.Lambda<Func<IInspectable, object>>(
Expression.New(classType.GetConstructor(BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] { interfaceType }, null),
createInterfaceInstanceExpression),
parms).Compile();
}

}

struct ComInterfaceEntry
Expand Down
21 changes: 17 additions & 4 deletions WinRT.Runtime/GuidGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static string GetSignature(Type type)
return "{" + type.GUID.ToString() + "}";
}

private static Guid encode_guid(byte[] data)
private static Guid encode_guid(Span<byte> data)
{
if (BitConverter.IsLittleEndian)
{
Expand All @@ -120,7 +120,11 @@ private static Guid encode_guid(byte[] data)
// encode rfc clock/reserved field
data[8] = (byte)((data[8] & 0x3f) | 0x80);
}
return new Guid(data.Take(16).ToArray());
#if NETSTANDARD2_0
return new Guid(data.Slice(0, 16).ToArray());
#else
return new Guid(data[0..16]);
#endif
}

private static Guid wrt_pinterface_namespace = new Guid("d57af411-737b-c042-abae-878b1e16adee");
Expand All @@ -132,11 +136,20 @@ public static Guid CreateIID(Type type)
{
return new Guid(sig);
}
#if NETSTANDARD2_0
var data = wrt_pinterface_namespace.ToByteArray().Concat(UTF8Encoding.UTF8.GetBytes(sig)).ToArray();
#else
var maxBytes = UTF8Encoding.UTF8.GetMaxByteCount(sig.Length);

var data = new byte[16 /* Number of bytes in a GUID */ + maxBytes];
Span<byte> dataSpan = data;
wrt_pinterface_namespace.TryWriteBytes(dataSpan);
var numBytes = UTF8Encoding.UTF8.GetBytes(sig, dataSpan[16..]);
data = data[..(16 + numBytes)];
#endif
using (SHA1 sha = new SHA1CryptoServiceProvider())
{
var hash = sha.ComputeHash(data);
return encode_guid(hash);
return encode_guid(sha.ComputeHash(data));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion WinRT.Runtime/IInspectable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public enum TrustLevel
// IInspectable
[ObjectReferenceWrapper(nameof(_obj))]
[Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90")]
public class IInspectable
public partial class IInspectable
Copy link
Member

Choose a reason for hiding this comment

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

partial only to combine with IInspectable.net5.cs? #if NET5_0 might be simpler

Copy link
Member Author

Choose a reason for hiding this comment

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

Given that IInspectable implements an extra interface in .NET 5.0, we'd have to add quite a few #if NET5_0 lines throughout the file. It's easier IMO to have two separate files.

{
[Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90")]
public unsafe struct Vftbl
Expand Down
13 changes: 13 additions & 0 deletions WinRT.Runtime/IInspectable.net5.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Concurrent;

namespace WinRT
{
public partial class IInspectable : IWinRTObject
{
IObjectReference IWinRTObject.NativeObject => _obj;

ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> IWinRTObject.QueryInterfaceCache { get; } = new();
}

}
77 changes: 77 additions & 0 deletions WinRT.Runtime/IWinRTObject.net5.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using WinRT.Interop;

namespace WinRT
{
public interface IWinRTObject : IDynamicInterfaceCastable
{
bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
{
if (QueryInterfaceCache.ContainsKey(interfaceType))
{
return true;
}
Type type = Type.GetTypeFromHandle(interfaceType);
Type helperType = type.FindHelperType();
if (helperType is null || !helperType.IsInterface)
{
return false;
}
int hr = NativeObject.TryAs<IUnknownVftbl>(GuidGenerator.GetIID(helperType), out var objRef);
if (hr < 0)
{
if (throwIfNotImplemented)
{
ExceptionHelpers.ThrowExceptionForHR(hr);
}
return false;
}
var vftblType = helperType.GetNestedType("Vftbl");
if (vftblType is null)
{
// The helper type might not have a vftbl type if it was linked away.
// The only time the Vftbl type would be linked away is when we don't actually use
// any of the methods on the interface (it was just a type cast/"is Type" check).
// In that case, we can use the IUnknownVftbl-typed ObjectReference since
// it has all of the information we'll need.
if (!QueryInterfaceCache.TryAdd(interfaceType, objRef))
{
objRef.Dispose();
}
return true;
}
if (vftblType.IsGenericTypeDefinition)
{
vftblType = vftblType.MakeGenericType(interfaceType.GetType().GetGenericArguments());
}
using (objRef)
{
IObjectReference typedObjRef = (IObjectReference)typeof(IObjectReference).GetMethod("As", Type.EmptyTypes).MakeGenericMethod(vftblType).Invoke(objRef, null);
if (!QueryInterfaceCache.TryAdd(interfaceType, typedObjRef))
{
typedObjRef.Dispose();
}
return true;
}
}

RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType)
{
var helperType = Type.GetTypeFromHandle(interfaceType).GetHelperType();
if (helperType.IsInterface)
return helperType.TypeHandle;
return default;
}

IObjectReference NativeObject { get; }

protected ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> QueryInterfaceCache { get; }

IObjectReference GetObjectReferenceForType(RuntimeTypeHandle type)
{
return QueryInterfaceCache[type];
}
}
}
40 changes: 22 additions & 18 deletions WinRT.Runtime/ObjectReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,32 @@ public unsafe ObjectReference<T> As<T>(Guid iid)
return ObjectReference<T>.Attach(ref thatPtr);
}

public unsafe TInterface AsInterface<TInterface>()
{
if (typeof(TInterface).GetCustomAttribute(typeof(System.Runtime.InteropServices.ComImportAttribute)) is object)
{
Guid iid = typeof(TInterface).GUID;
public unsafe TInterface AsInterface<TInterface>()
{
if (typeof(TInterface).GetCustomAttribute(typeof(System.Runtime.InteropServices.ComImportAttribute)) is object)
{
Guid iid = typeof(TInterface).GUID;
Marshal.ThrowExceptionForHR(VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr comPtr));
try
{
return (TInterface)Marshal.GetObjectForIUnknown(comPtr);
}
finally
{
var vftblPtr = Unsafe.AsRef<WinRT.VftblPtr>(comPtr.ToPointer());
var vftblIUnknown = Marshal.PtrToStructure<WinRT.Interop.IUnknownVftbl>(vftblPtr.Vftbl);
vftblIUnknown.Release(comPtr);
}
}

try
{
return (TInterface)Marshal.GetObjectForIUnknown(comPtr);
}
finally
{
var vftblPtr = Unsafe.AsRef<WinRT.VftblPtr>(comPtr.ToPointer());
var vftblIUnknown = Marshal.PtrToStructure<WinRT.Interop.IUnknownVftbl>(vftblPtr.Vftbl);
vftblIUnknown.Release(comPtr);
}
}

#if NETSTANDARD2_0
return (TInterface)typeof(TInterface).GetHelperType().GetConstructor(new[] { typeof(IObjectReference) }).Invoke(new object[] { this });
#else
return (TInterface)(object)new WinRT.IInspectable(this);
#endif
}

public int TryAs<T>(out ObjectReference<T> objRef) => TryAs<T>(GuidGenerator.GetIID(typeof(T)), out objRef);
public int TryAs<T>(out ObjectReference<T> objRef) => TryAs(GuidGenerator.GetIID(typeof(T)), out objRef);

public virtual unsafe int TryAs<T>(Guid iid, out ObjectReference<T> objRef)
{
Expand Down
Loading