microsoft / CsWinRT

C# language projection for the Windows Runtime
MIT License
534 stars 102 forks source link

Type Mapping initialization - how to ensure cswinrt owns type mappings #89

Closed AdamBraden closed 4 years ago

jkoritzinsky commented 4 years ago

Custom Type Mapping Ownership

For most WinRT types, the CsWinRT tools owns both the "public" type and the "ABI" type. However, in many cases we project a WinRT type to an equivalent type in .NET to enable developers to use the type ecosystem that they're used to. These types include the fundamental types as well as many common collection types. As a result, it is possible for a component to have no references to the Windows SDK (types under the Windows.* namespace that aren't under WUX in WinRT). Additionally, because of the WUX/MUX split, we have numerous cases where there are multiple WinRT types for a given .NET type, which also poses a problem for constructing a Com-Callable-Wrapper for any given .NET object efficiently.

Approach 1: Each projected assembly own the the type mappings in its projected namespaces

Pros:

Cons:

Approach 2: The WinRT.Runtime assembly owns all of the type mappings

Pros:

Cons:

Approach 3: The WinRT.Runtime assembly owns the Windows SDK (non-WUX, non-MUX) mappings and the projections of WUX/MUX own their mappings

Pros:

Cons:

jkoritzinsky commented 4 years ago

@stevenbrix Great read!! This is well articulated. I just have a few questions :)

Additionally, because of the WUX/MUX split

AFAIK, WUX is not ever going to move to cs/winrt, it will stay on .NET Native. Does that change things?

By WUX I mean any usage of types under the Windows.UI.Xaml namespace both inside and outside of an AppContainer. This includes Xaml Islands and any other usage of these types in a .NET Core application. Since today you can instantiate WUX types on .NET Core outside of .NET Native and we want to move this usage to cswinrt projections as well, this is still a problem.

Any projections that have to instantiate new objects to pass across the wire will require manually maintained projections of the interface/ABI types for any interfaces they implement.

Can you explain what this means? Perhaps with code samples if applicable?

For example, the manually maintained projection of ICustomPropertyProvider will have to provide implementations of ICustomProperty. These implementations would require the projected definition of ICustomProperty to be available in the WinRT.Runtime assembly. Since we wouldn't want it to always live there (we'd want it to live with the projection of its namespace), we'd likely make an internal copy of the projection at the time of writing. So there'd be two copies. It wouldn't be terrible, but it would be an annoyance.

If WinUI keeps Windows.UI.Xaml.Interop.TypeName in the WUX namespace for WinUI 3, the main problem this solution avoids is still a problem (the first con under approach 2).

We are planning on moving this to Microsoft.UI.Xaml.Interop.TypeName by the time we ship

Awesome! That will solve the problem partially. As long as someone is using exclusively WUX or MUX we can avoid dealing with the problem and just tell people to fully move to one or the other.

stevenbrix commented 4 years ago

Since today you can instantiate WUX types on .NET Core outside of .NET Native and we want to move this usage to cswinrt projections as well, this is still a problem.

Do we want to support those projections? Will any existing WUX library (say the existing Telerik library) just work, or will they have to re-write to target .NET5 (and then we want them to target WinUI instead)?

jkoritzinsky commented 4 years ago

I believe from the .NET side we wanted to support these projections. Everyone would have to recompile, but they would have a path forward without changing namespaces. @AaronRobinsonMSFT would have more info on this (he was on the email thread a while back).

AaronRobinsonMSFT commented 4 years ago

In general our desire is to require minimal code changes for this transition from the built-in to the CsWinRT approach.

There is some latitude, but it comes down to UX and how much work to we put on users to adopt the new mechanism. As @jkoritzinsky stated, our desire was to move forward without changing namespaces for the WUX scenario - obviously MUX would require that. The overall UX is mostly owned by a non-.NET team, but our desire is to make .NET customers' move simple. If there was extensive documentation about the changes (limited to a page or two) that was easy to find that might be okay.

A proposal for the changes would be helpful in understanding the UX impact and let us provide definitive feedback.

stevenbrix commented 4 years ago

@LucasHaines, can you follow up on this? We are planning on a conversion tool from WUX->MUX, so I think we should be comfortable not supporting the old way. We should maybe get started on the tool sooner rather than later so we know if it will be a viable solution.

jkoritzinsky commented 4 years ago

There's an additional problem to Approach 1 and 3 that I missed before I started prototyping. We'll hit the code-duplication problem in Approach 1 100% of the time because we need the vtable types for every interface we're automatically implementing to correctly supply them to the CCW. So we'll have to copy those as well.

jkoritzinsky commented 4 years ago

As a result, I'll probably wait to actually implement these changes until we can get a build of the sdk with function pointers and native-callable since those two features will cause a lot of changes in our vtable type generation.

BenJKuhn commented 4 years ago

Model three will scale best over time. I hope it works out that you can resolve the code duplication concerns.

jkoritzinsky commented 4 years ago

I did think of another issue with Approach 3 though, although I believe it is a very esoteric scenario. If you have a managed component (which I believe we were already on the fence about supporting initially) that provides your view models and model data and doesn't reference WinUI and you write your UI in native, then the WinUI-specific auto-added interfaces like ICustomPropertyProvider wouldn't light up.

If we adjust Approach 3 to be "all auto-implemented interfaces live in WinRT.Runtime", then that solves this concern. WinUI-specific type mappings would still travel with WinUI, but the auto-implemented interfaces would always exist even when WinUI isn't being used. Since we already have to do some code-duplication anyway for the vtables for IPropertyInfo and IReference and family, the additional code for defining the interface and vtable types for ICustomPropertyProvider and the like isn't enough to concern me.

jkoritzinsky commented 4 years ago

The more I think about it, the problem I mentioned about about a managed viewmodel component with the UI in native also breaks if the rest of the WinUI projections for INPC, INCC, and so on aren't provided. I think the easiest way to handle this will be that WinUI owns all the WinUI stuff even the auto-implemented interfaces since we don't support managed components anyway. If we decide to support managed components in the future, we can decide if we want to solve this problem then.

stevenbrix commented 4 years ago

since we don't support managed components anyway.

Let's make sure this is the plan of action. I've been advocating we don't, but we might have customers that are dependent on this. @Scottj1s is under the impression that winmdexp should work the same today as it always has, is that true?

jkoritzinsky commented 4 years ago

Winmdexp only supports the old way of doing projections. There would have to be a new tool written to support the new way of projections with cswinrt.

AdamBraden commented 4 years ago

Will 'not supporting managed components in any way' impact activation scenarios? We still need to ensure managed OOP backgroundtasks can be registered in the manifest and activated.

stevenbrix commented 4 years ago

We still need to ensure managed OOP backgroundtasks can be registered in the manifest and activated.

Do you have an app sample that I could look at to get a better understanding of how this works?

AdamBraden commented 4 years ago

https://docs.microsoft.com/en-us/windows/uwp/launch-resume/create-and-register-a-background-task and https://github.com/microsoft/Windows-universal-samples/tree/master/Samples/BackgroundTask/cs

jkoritzinsky commented 4 years ago

Yes if we want to support managed background tasks like that then we need to support managed components written with cswinrt.

stevenbrix commented 4 years ago

Yup, agreed. Thanks Adam! I've updated the spec here to reflect this: https://github.com/microsoft/CsWinRT/issues/77

AaronRobinsonMSFT commented 4 years ago

Yes if we want to support managed background tasks like that then we need to support managed components written with cswinrt.

I believe @jkoritzinsky mentioned this last week. It was in relation to the WinRTHost.dll and how it was similar to our ComHost.dll. The existing WinRTHost.dll is tightly coupled with our built-in support for WinRT so is more a proof of concept rather than a long term solution. We have options for how the WinUI or CsWinRT team can author and own the official hosting mechanism or we can discuss how we can own that part. Our starting stance would be for the .NET team to not own this component, but that is merely a starting point in the design.

In general the hosting technology and mechanism isn't that interesting from the runtime side of things so I wouldn't get too concerned about how it works from the runtime activation and discovery stand point. The design of how CsWinRT and associated CsWinRT runtime assembly activation and hydrate the ABI and default ComWrappers are the primary areas I would work on understanding.

jkoritzinsky commented 4 years ago

So it sounds like we're going to have to go the route of Approach 2 and solve the problems associated with it.

Scottj1s commented 4 years ago

I would be leaning towards approach 2 as well. A couple clarifying questions?

Is it really under consideration that WinUI would (re)use the WUX namespace? seems like that would create chaos with both the SDK and WinUI providing overlapping types. I thought that was the reason MUX was created to begin with.

Along the same vein, mixing and matching WUX and MUX in the same process seems fraught with peril. If that too can be eliminated from consideration, than there are really only two concerns I have: The ambiguity of which dll hosts a given type (e.g., WUX vs MUX), The need to load the interop dll lazily (on demand) for any mapped type

Given the set of custom mappings is fixed, and unlikely to grow, hardcoding that metadata into the runtime shouldn't be a concern: Projected->ABI type mappings ABI->dll mappings, by priority (e.g., winui.dll over sdk.dll)

jkoritzinsky commented 4 years ago

Along the same vein, mixing and matching WUX and MUX in the same process seems fraught with peril. If that too can be eliminated from consideration, than there are really only two concerns I have: The ambiguity of which dll hosts a given type (e.g., WUX vs MUX), The need to load the interop dll lazily (on demand) for any mapped type

The idea is that all of the interop projections would live in WinRT.Runtime. We wouldn't do any lazy assembly loading because that breaks trimming and is very slow. We would have to solve the generics-of-structs multiple PIID problem, but I should be able to start prototyping solutions next week.

Given the set of custom mappings is fixed, and unlikely to grow, hardcoding that metadata into the runtime shouldn't be a concern: Projected->ABI type mappings ABI->dll mappings, by priority (e.g., winui.dll over sdk.dll)

We'll encode this into WinRT.Runtime with a parallel table to the one in cswinrt.exe.

jkoritzinsky commented 4 years ago

I believe we have solved this, so I'm going to close this issue.

For anyone looking back on this issue in the future, we took a modified version of Approach 2 where all projections to .NET types live in WinRT.Runtime and all projections from a WinRT type to a manual reimplementation of that type with the same namespace and type name live with the rest of the automatically projected namespace. This enables us to not have to worry about type mapping initialization since the system already understands these projections intrinsically (since they live in WinRT.Runtime) or they light up automatically like regular projections (since their code will light up similarly to auto-generated code).