Skip to content

Commit

Permalink
Added: Assembly Trimming Annotations for SharpGen.Runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
Sewer56 committed Jun 26, 2022
1 parent e10f5c6 commit 1fbe2cf
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 28 deletions.
6 changes: 6 additions & 0 deletions SharpGen.Runtime.Trim.Dummy/Program.cs
@@ -0,0 +1,6 @@
// See https://aka.ms/new-console-template for more information

// I'm a dummy project for testing trimmability, since the analyzer outside of publish time isn't fully perfect yet
// test my trimming with `dotnet publish -r win-x64`

Console.WriteLine("Hello SharpGenTools");
20 changes: 20 additions & 0 deletions SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishTrimmed>true</PublishTrimmed>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\SharpGen.Runtime\SharpGen.Runtime.csproj" />
</ItemGroup>

<ItemGroup>
<TrimmerRootAssembly Include="SharpGen.Runtime" />
</ItemGroup>

</Project>
37 changes: 31 additions & 6 deletions SharpGen.Runtime/COM/ComObject.cs
Expand Up @@ -21,6 +21,7 @@
#nullable enable

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
Expand Down Expand Up @@ -110,7 +111,11 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
/// <msdn-id>ms682521</msdn-id>
/// <unmanaged>IUnknown::QueryInterface</unmanaged>
/// <unmanaged-short>IUnknown::QueryInterface</unmanaged-short>
public virtual T QueryInterface<T>() where T : ComObject
public virtual T QueryInterface<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>() where T : ComObject
{
QueryInterface(typeof(T).GetTypeInfo().GUID, out var parentPtr).CheckError();
return MarshallingHelpers.FromPointer<T>(parentPtr)!;
Expand All @@ -128,7 +133,11 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public static T As<T>(object comObject) where T : ComObject => As<T>(Marshal.GetIUnknownForObject(comObject));
public static T As<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(object comObject) where T : ComObject => As<T>(Marshal.GetIUnknownForObject(comObject));

/// <summary>
/// Queries a managed object for a particular COM interface support (This method is a shortcut to <see cref="QueryInterface"/>)
Expand All @@ -139,7 +148,11 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
/// <msdn-id>ms682521</msdn-id>
/// <unmanaged>IUnknown::QueryInterface</unmanaged>
/// <unmanaged-short>IUnknown::QueryInterface</unmanaged-short>
public static T As<T>(IntPtr iunknownPtr) where T : ComObject
public static T As<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(IntPtr iunknownPtr) where T : ComObject
{
using var tempObject = new ComObject(iunknownPtr);
return tempObject.QueryInterface<T>();
Expand All @@ -157,7 +170,11 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public static T QueryInterface<T>(object comObject) where T : ComObject =>
public static T QueryInterface<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(object comObject) where T : ComObject =>
As<T>(Marshal.GetIUnknownForObject(comObject));

/// <summary>
Expand All @@ -169,7 +186,11 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
/// <msdn-id>ms682521</msdn-id>
/// <unmanaged>IUnknown::QueryInterface</unmanaged>
/// <unmanaged-short>IUnknown::QueryInterface</unmanaged-short>
public static T? QueryInterfaceOrNull<T>(IntPtr comPointer) where T : ComObject
public static T? QueryInterfaceOrNull<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(IntPtr comPointer) where T : ComObject
{
using var tempObject = new ComObject(comPointer);
return tempObject.QueryInterfaceOrNull<T>();
Expand All @@ -183,7 +204,11 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
/// <msdn-id>ms682521</msdn-id>
/// <unmanaged>IUnknown::QueryInterface</unmanaged>
/// <unmanaged-short>IUnknown::QueryInterface</unmanaged-short>
public virtual T? QueryInterfaceOrNull<T>() where T : ComObject
public virtual T? QueryInterfaceOrNull<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>() where T : ComObject
{
return MarshallingHelpers.FromPointer<T>(QueryInterfaceOrNull(typeof(T).GetTypeInfo().GUID));
}
Expand Down
28 changes: 24 additions & 4 deletions SharpGen.Runtime/CallbackBase.Reflection.cs
Expand Up @@ -2,19 +2,28 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using SharpGen.Runtime.TrimmingWrappers;

namespace SharpGen.Runtime;

public abstract partial class CallbackBase
{
private readonly struct ImmediateShadowInterfaceInfo
{
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
public readonly TypeInfo Type;
public readonly List<TypeInfo> ImplementedInterfaces;

public ImmediateShadowInterfaceInfo(TypeInfo type)
public ImmediateShadowInterfaceInfo(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
TypeInfo type)
{
Type = type;
ImplementedInterfaces = new(6);
Expand All @@ -35,16 +44,27 @@ public ImmediateShadowInterfaceInfo(TypeInfo type)
// Cache reflection on interface inheritance
private class CallbackTypeInfo
{
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
private readonly TypeInfo type;
private ImmediateShadowInterfaceInfo[]? _vtbls;
private TypeInfo[]? _shadows;
private Guid[]? _guids;

public CallbackTypeInfo(Type type) : this(type.GetTypeInfo())
public CallbackTypeInfo(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
Type type) : this(type.GetTypeInfo())
{
}

private CallbackTypeInfo(TypeInfo type)
private CallbackTypeInfo(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
TypeInfo type)
{
this.type = type ?? throw new ArgumentNullException(nameof(type));
}
Expand Down Expand Up @@ -111,7 +131,7 @@ private ImmediateShadowInterfaceInfo[] BuildVtblList()

foreach (var implementedInterface in type.ImplementedInterfaces)
{
var item = implementedInterface.GetTypeInfo();
var item = implementedInterface.GetTypeInfoWithPreservedInterfaces();

// Only process interfaces that have vtbl
if (!VtblAttribute.Has(item))
Expand Down
3 changes: 2 additions & 1 deletion SharpGen.Runtime/CallbackBase.ReflectionCache.cs
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using SharpGen.Runtime.TrimmingWrappers;

namespace SharpGen.Runtime;

Expand All @@ -12,7 +13,7 @@ public abstract partial class CallbackBase
private CallbackTypeInfo GetTypeInfo()
{
CallbackTypeInfo? info;
var type = GetType();
var type = this.GetTypeWithPreservedInterfaces();
var cache = TypeReflectionCache;

lock (cache)
Expand Down
11 changes: 10 additions & 1 deletion SharpGen.Runtime/CallbackBase.ReflectionImpl.cs
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
Expand All @@ -13,7 +14,11 @@ public abstract unsafe partial class CallbackBase
{
protected virtual Guid[] BuildGuidList() => GetTypeInfo().Guids;

private GCHandle CreateShadow(TypeInfo type)
private GCHandle CreateShadow(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
#endif
TypeInfo type)
{
var shadow = (CppObjectShadow) Activator.CreateInstance(type.AsType())!;

Expand All @@ -23,6 +28,10 @@ private GCHandle CreateShadow(TypeInfo type)
return GCHandle.Alloc(shadow, GCHandleType.Normal);
}

#if NET6_0_OR_GREATER
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062", Justification = $"{nameof(ShadowAttribute.Type)} is already marked `DynamicallyAccessedMemberTypes.PublicConstructors` and the existing check via `Debug.Assert(holder.GetTypeInfo().GetConstructor(Type.EmptyTypes)` will ensure correctness.")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111", Justification = "Same as above.")]
#endif
protected virtual void InitializeCallableWrappers(IDictionary<Guid, IntPtr> ccw)
{
// Associate all shadows with their interfaces.
Expand Down
6 changes: 5 additions & 1 deletion SharpGen.Runtime/InterfaceArray.cs
Expand Up @@ -33,7 +33,11 @@ namespace SharpGen.Runtime;
[DebuggerTypeProxy(typeof(InterfaceArray<>.InterfaceArrayDebugView))]
[DebuggerDisplay("Count={" + nameof(Length) + "}")]
[SuppressMessage("ReSharper", "ConvertToAutoProperty")]
public unsafe struct InterfaceArray<T> : IReadOnlyList<T>, IEnlightenedDisposable, IDisposable
public unsafe struct InterfaceArray<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T> : IReadOnlyList<T>, IEnlightenedDisposable, IDisposable
where T : CppObject
{
// .NET Native has issues with <...> in property backing fields in structs
Expand Down
13 changes: 11 additions & 2 deletions SharpGen.Runtime/MarshallingHelpers.cs
@@ -1,6 +1,7 @@
#nullable enable

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace SharpGen.Runtime;
Expand All @@ -13,7 +14,11 @@ public static partial class MarshallingHelpers
/// <typeparam name="T">The CppObject class that will be returned</typeparam>
/// <param name="cppObjectPtr">The native pointer to a C++ object.</param>
/// <returns>An instance of T bound to the native pointer</returns>
public static T? FromPointer<T>(IntPtr cppObjectPtr) where T : CppObject
public static T? FromPointer<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(IntPtr cppObjectPtr) where T : CppObject
{
if (cppObjectPtr == IntPtr.Zero)
return default;
Expand All @@ -31,7 +36,11 @@ public static partial class MarshallingHelpers
/// <typeparam name="T">The CppObject class that will be returned</typeparam>
/// <param name="cppObjectPtr">The native pointer to a C++ object.</param>
/// <returns>An instance of T bound to the native pointer</returns>
public static T? FromPointer<T>(UIntPtr cppObjectPtr) where T : CppObject
public static T? FromPointer<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(UIntPtr cppObjectPtr) where T : CppObject
{
if (cppObjectPtr == UIntPtr.Zero)
return default;
Expand Down
10 changes: 9 additions & 1 deletion SharpGen.Runtime/ShadowAttribute.cs
Expand Up @@ -20,6 +20,7 @@

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;

Expand All @@ -34,13 +35,20 @@ public sealed class ShadowAttribute : Attribute
/// <summary>
/// Type of the associated shadow
/// </summary>
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
public Type Type { get; }

/// <summary>
/// Initializes a new instance of <see cref="ShadowAttribute"/> class.
/// </summary>
/// <param name="holder">Type of the associated shadow</param>
public ShadowAttribute(Type holder)
public ShadowAttribute(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
Type holder)
{
Type = holder ?? throw new ArgumentNullException(nameof(holder));

Expand Down
3 changes: 2 additions & 1 deletion SharpGen.Runtime/SharpGen.Runtime.csproj
Expand Up @@ -5,6 +5,7 @@

<PropertyGroup>
<!--
net6.0 for assembly trimming. [Technically supported with .NET 5, albeit would need to replace DynamicallyAccessedMemberTypes.Interfaces` with `All`.
net5.0 for function pointers codegen and Native(U)Long API surface
netcoreapp3.0 for smaller [empty] dependency tree (w/o System.Runtime.CompilerServices.Unsafe)
netstandard2.1 + netcoreapp2.1 for smaller dependency tree (w/o System.Memory)
Expand All @@ -15,7 +16,7 @@
net45 for smaller dependency tree for all .NET Framework versions
netstandard1.3 is the lowest supported version (except .NET Framework)
-->
<TargetFrameworks>net5.0;netcoreapp3.0;netstandard2.1;netcoreapp2.1;netstandard2.0;net471;net46;net45;netstandard1.3</TargetFrameworks>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.0;netstandard2.1;netcoreapp2.1;netstandard2.0;net471;net46;net45;netstandard1.3</TargetFrameworks>
<Description>Support classes for code generated by SharpGen.</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
Expand Down
28 changes: 28 additions & 0 deletions SharpGen.Runtime/TrimmingWrappers/TrimmingExtensions.cs
@@ -0,0 +1,28 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace SharpGen.Runtime.TrimmingWrappers
{
internal static class TrimmingExtensions
{
#if NET6_0_OR_GREATER
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2068", Justification = "We're preserving nested interfaces via wrapper method.")]
#endif
public static TypeInfo GetTypeInfoWithPreservedInterfaces(this Type type)
{
return type.GetTypeInfo();
}


#if NET6_0_OR_GREATER
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2073", Justification = "We're preserving nested interfaces via wrapper method.")]
#endif
public static Type GetTypeWithPreservedInterfaces(this object obj)
{
return obj.GetType();
}
}
}
15 changes: 12 additions & 3 deletions SharpGen.Runtime/TypeDataStorage.cs
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using SharpGen.Runtime.TrimmingWrappers;

namespace SharpGen.Runtime;

Expand Down Expand Up @@ -117,7 +118,11 @@ static TypeDataStorage()
return null;
}

internal static bool GetTargetVtbl(TypeInfo type, out void* pointer)
internal static bool GetTargetVtbl(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
TypeInfo type, out void* pointer)
{
#if !FORCE_REFLECTION_ONLY
if (vtblByGuid.TryGetValue(type.GUID, out var ptr))
Expand All @@ -137,7 +142,11 @@ internal static bool GetTargetVtbl(TypeInfo type, out void* pointer)
return false;
}

private static IntPtr RegisterFromReflection(TypeInfo type, IntPtr[] sourceVtbl)
private static IntPtr RegisterFromReflection(
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
TypeInfo type, IntPtr[] sourceVtbl)
{
var callbackable = typeof(ICallbackable).GetTypeInfo();

Expand All @@ -146,7 +155,7 @@ private static IntPtr RegisterFromReflection(TypeInfo type, IntPtr[] sourceVtbl)

foreach (var iface in type.ImplementedInterfaces)
{
var typeInfo = iface.GetTypeInfo();
var typeInfo = iface.GetTypeInfoWithPreservedInterfaces();
if (callbackable == typeInfo || !callbackable.IsAssignableFrom(typeInfo))
continue;

Expand Down

0 comments on commit 1fbe2cf

Please sign in to comment.