microsoft/CsWinRT

Handle scenarios when the transitive closure of types is known.

AaronRobinsonMSFT opened this issue · 12 comments

There are scenarios in WinRT where the transitive closure of all types is known. This would imply there are opportunities to optimize the RCW code generation logic and avoid querying the runtime for these types - see

internal static Func<IInspectable, object> CreateTypedRcwFactory(string runtimeClassName)

Reasons for considering this are: AOT and linkability (reduce memory footprint).

The default for the WinRT (store) apps should be the constrained environment, where the transitive closure of all types is known, without reflection fallbacks. Otherwise, the WinRT apps are going to be big and slow by default.

Would it make sense to have an explicit opt-in (e.g. API call) to enable the reflection fallbacks, so that only apps that want to pay for it get it?

Another option would be to expose an API to allow an app to pre-register its factories. Then in cases where the transitive type closure is known, the entire table could be pre-registered at startup before the app runs.

We could make the reflection fallback opt-in if we feel that there are users who would rather have the code throw an exception than go down the reflection path.

@jkoritzinsky Would CsWinRT expose this new API or the runtime for CsWinRT to query? I would prefer the former rather than the latter. I think providing some pseudo code that illustrates how Apps and CsWinRT would interact would be helpful to illustrate the idea.

CsWinRT would expose this API and code generated by some task in the SDK toolchain would call it at startup either in a module initializer or a Main method. I’ll post some possible pseudo code tomorrow.

Here's the API I'm thinking of. We can do either or both depending on what we think is better (or what gives better perf).

public partial static class ComWrappersSupport
{
     public static void RegisterObjectFactoryForRuntimeClass(string runtimeClassName, Func<WinRT.IInspectable, object> factory);

     public static void RegisterObjectFactoriesForRuntimeClass(params System.Collections.Generic.KeyValuePair<string, Func<WinRT.IInspectable, object>>[] factories);

}

The usage would be something like as follows:

public static void Main()
{
     WinRT.ComWrappersSupport.RegisterObjectFactoryForRuntimeClass("Windows.Foundation.IReference`1<Int32>", inspectable => new ABI.Windows.Foundation.IReference<int>(inspectable.As<ABI.Windows.Foundation.IReference<int>.Vftbl>()));
// Run the WinUI application here.
}

This would require us to make the types in the ABI namespace public instead of their current internal visibility.

@jkoritzinsky Perhaps it would be better to have the caller provide a ComWrappers instance instead? I would like to limit ways to create external object wrappers and the ComWrappers already exists so it might make sense to reuse that.

We could do that as well if we're ok with this API being available only on the .NET 5 target.

Unless we want to play the type-fowarding game and define a ComWrappers type in CsWinRT in System.Runtime.InteropServices that type-forwards to the runtime-defined one on .NET 5.

We could do that as well if we're ok with this API being available only on the .NET 5 target.

The non .NET 5 approach is already degraded due to the inability to properly interact with a tracker runtime (e.g. Jupiter) and the more expensive object identity and lifetime tracking mechanisms that are implemented in .NET Standard 2.0. I think that would be okay. I am not entirely convinced this API would provide the most optimized/AOT-friendly approach though.

I meant define it in the System.Runtime.InteropServices namespace in a singular location (since some of the CsWinRT code is going to have to be shared across all projections anyway) that multitargets netstandard2.0 and net5 and type-forwards on the net5 target.

I don't particularly like the idea, but we could do it.

Here's the API I'm thinking of

It would be nice if this API allows hydrating the registrations lazily. E.g. If the app has 1000 types but only one of them is actually created at runtime, the app should only pay for that one type.

CsWinRT would expose this API and code generated by some task in the SDK toolchain would call it at startup either in a module initializer or a Main method. I’ll post some possible pseudo code tomorrow.

@jkoritzinsky is there work planned for the sdk toolchain to support this? And I'm guessing you are referring to the Windows SDK?