CS/WinRT and WinUI integration
See original GitHub issueCS/WinRT and WinUI integration spec
Outline
This spec has the following high-level goals:
- Detail the fundamental principles of how CS/WinRT interop assemblies will work
- Detail the type projections
- Callout any outstanding questions or issues related to how CS/WinRT will work
Table of Contents
Overview of CS/WinRT
This section aims to give a high level overview of the CS/WinRT project, and some basic principles of how the tool works.
What is CS/WinRT?
CS/WinRT is a new language projection implementation to allow the .NET runtime to interop with the Windows Runtime (WinRT). The Windows Runtime is a set of libraries, whose APIs are defined in the .winmd
file format.
Why CS/WinRT?
The WinRT API surface defines many APIs that are very similar to those that live in .NET. In order to make .NET development feel more natural to .NET developers, there are a set of The current WinRT projections are baked into the .NET Native runtime and compiler. This special and hardcoded knowledge has proved problematic and buggy, and does not scale. The goal with CS/WinRT is to create the proper layer of abstraction for these projections, so that the .NET runtime has no special knowledge of WinRT, and instead just operates on the assemblies like they are regular .NET assemblies (because they are!)
When will CS/WinRT ship?
The plan is to have CS/WinRT ready for the release of .NET5, to be consumed by WinUI3.0. It will ship as a Nuget package on nuget.org.
How does CS/WinRT work?
At a conceptually high level, CS/WinRT works much like Cpp/WinRT. The tool, cswinrt.exe
creates wrapper classes, defined in CSharp, that understand the Application Binary Interface (ABI) of the WinRT libraries based off of the .winmd
files that are used as inputs.
There will be a single CS/WinRT runtime interop dll (name TBD) that is shared between any CS/WinRT interop assembly. TBD more on this
TODO: Link to tool implementation and/or spec
WinRT to .NET Projections
Foundational types
Below is a list of the foundational types (as they show in the .winmd
) and the corresponding .NET type they project to. While many of these are primitive types, this list also consists of many other basic types such as collections.
WinRT Type | Projected .NET Type |
---|---|
Windows.Foundation.Object |
System.Object |
Windows.Foundation.Int32 |
System.Int32 |
Windows.Foundation.Int64 |
System.Int64 |
Windows.Foundation.UInt32 |
System.UInt32 |
Windows.Foundation.UInt32 |
System.UInt64 |
Windows.Foundation.Object |
System.Object |
Interfaces
TODO: Finish filling this in with collection types
WinRT Type | Projected .NET Type |
---|---|
Microsoft.UI.Xaml.Input.ICommand |
System.Windows.Input.ICommand |
Microsoft.UI.Xaml.Interop.IBindableIterable |
System.Collections.IEnumerable |
Microsoft.UI.Xaml.Interop.IBindableVector |
System.Collections.IList |
Microsoft.UI.Xaml.Inteorp.INotifyCollectionChanged |
System.UInt32 |
Microsoft.UI.Xaml.Data.INotifyPropertyChanged |
System.ComponentModel.INotifyPropertyChanged |
Microsoft.UI.Xaml.Data.INotifyDataErrorInfo |
System.ComponentModel.INotifyDataErrorInfo |
Structs
The following types don’t have a projection into .NET, although the structs do have more functionality than a classic WinRT struct.
TODO: more on why these aren’t projected to the System.Windows.*
types, and should they?
WinRT Type |
---|
Windows.Foundation.Point |
Windows.Foundation.Rect |
Windows.Foundation.Size |
Struct Helper classes
Xaml types that get extra members in the CSharp projection.
For example, Xaml’s CornerRadius is just a struct:
struct CornerRadius
{
DOUBLE TopLeft;
DOUBLE TopRight;
DOUBLE BottomRight;
DOUBLE BottomLeft;
};
But when it’s projected in CSharp, it acquires a constructor and comparison operators:
public struct CornerRadius
{
public CornerRadius(double topLeft, double topRight, double bottomRight, double bottomLeft);
public double BottomLeft { get; set; }
public double BottomRight { get; set; }
public double TopLeft { get; set; }
public double TopRight { get; set; }
public override bool Equals(object obj);
public bool Equals(CornerRadius cornerRadius);
public override int GetHashCode();
public override string ToString();
public static bool operator ==(CornerRadius cr1, CornerRadius cr2);
public static bool operator !=(CornerRadius cr1, CornerRadius cr2);
}
There is actually a way for non-CSharp developers to get access to this same functionality with some Helper classes. For example, there’s a CornerRadiusHelper class that has these members on it.
These Helper classes are not projected by CSharp, since the functionality is on the structs already.
Note that Point/Rect/Size are Windows.Foundation structs, but the helper classes are in Xaml’s namespace (e.g. RectHelper).
The structs and their helpers:
Struct | Struct helper |
---|---|
CornerRadius |
CornerRadiusHelper |
Duration |
DurationHelper |
DurationType |
|
GridLength |
GridLengthHelper |
Thickness |
ThicknessHelper |
GeneratorPosition |
GeneratorPositionHelper |
Matrix |
MatrixHelper |
KeyTime |
KeyTimeHelper |
RepeatBehavior |
RepeatBehaviorHelper |
RepeatBehaviorType |
Note that in addition to adding members to these structs, the projections add ToString() overrides. For example, the ToString for Thickness is something like “10,2,10,2” rather than just the type name.
Exceptions
These exception types live in System.Runtime.WindowsRuntime.UI.Xaml.dll
and are automatically created at runtime by the interop layer:
ElementNotAvailableException
ElementNotEnabledException
LayoutCycleException
XamlParseException
The Type Type
WinRT doesn’t officially have the notion of a type
. There is no Windows.Foundatation.Type
. You can ask an object
(IInspectable) for runtime class name, but that’s only a string.
But, Xaml has a type named Windows.UI.Xaml.Interop.TypeName
(and associated TypeKind
enum) that projects in CSharp to System.Type
.
With WinUI3, this type will eventually move to Microsoft.UI.Xaml.Interop.TypeName
, however this work has not been done yet because we are still using .NET Native. Once the project system work has been done to move off of .NET Native, we can move this type. Ideally, CS/WinRT already has support for Microsoft.UI.Xaml.Interop.TypeName
at that point, and we can make the change in metadata and move all of our test projects at the same time.
ICustomPropertyProvider
Xaml calls to CCWs today and QIs for ICustomPropertyProvider to get properties for data binding. The implementation of that interface uses reflection on the target .Net object.
TODO: More on how this interop will work
System IO
Some of the .NET APIs in the System.IO
namespace, for example, System.IO.File
were removed from the .NET Native API surface because they did not conform to the sandboxed environment of the app container. Instead, developers used the WinRT equivalent APIs, and to keep along with the example, would use Windows.Storage.StorageFile
instead. With moving to .NET5 and CS/WinRT, we want there to be a single runtime and a more uniform API surface area, however that still leaves what to do with these APIs unresolved.
TODO: Gather a complete list of these APIs
WinRT Type | .NET Type |
---|---|
Windows.Storage.StorageFile |
System.IO.File |
Windows.Storage.StorageFolder |
System.IO.Directory |
Streams
WinRT APIs that either returned or accepted a stream would use the Windows.Storage.Streams.IRandomAccessStream
class at the API surface. There existed some helper extension methods in the .NET/WinRT interop assembly. Rather than relying on those, can we instead just project the Windows.Storage.Streams.IRandomAccessStream
as a System.IO.Stream
? The extension methods can still exist so that code can compile with minimal changes, but will no longer be required. Another option would be to have the app conversion tool remove the use of these extension methods.
Consuming CS/WinRT
WinRT library authors will consume the CS/WinRT tool through the Nuget. For WinUI control authors writing C++ winrt components, this will be abstracted through some MSBuild targets. The MSBuild targets should generate the CSharp wrappers and compile the code into an interop assembly, and not require the need for a second project for doing so. If developers don’t have the CSharp workload installed, they can opt-out of this by setting an MSBuild property, although we don’t want this to be the default behavior. It should first be validated and confirmed how C++ WinRT component authors generate their Nuget packages. The Win2D Nuget is probably a good starting point for seeing how these Nugets are generated.
Authoring WinRT APIs in CSharp
Compiling CSharp WinRT components into a .winmd
allows for the following scenarios:
- C++ Applications referencing a C# component
- C# background tasks
In order to do this, the winmdexp
tool needs to be updated to work with the new projections.
Issue Analytics
- State:
- Created 4 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
Yeah, I’ll go ahead and do it for you 😃
@Scottj1s