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

Provide a fully embedded WinRT interop option #304

Closed
AaronRobinsonMSFT opened this issue Jun 10, 2020 · 17 comments · Fixed by #1008, #1009 or #1011
Closed

Provide a fully embedded WinRT interop option #304

AaronRobinsonMSFT opened this issue Jun 10, 2020 · 17 comments · Fixed by #1008, #1009 or #1011
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@AaronRobinsonMSFT
Copy link
Member

AaronRobinsonMSFT commented Jun 10, 2020

There are scenarios where the developer would like to use a WinRT API without polluting/altering their dependencies. This could potentially be limited to cases where the usage of the WinRT API is entirely internal to the library.

It would be nice if the CsWinRT tool could provide a comprehensive WinRT interop scenario, that is internal to the assembly, in the form of just source code - no WinRT.Runtime.dll dependency.

/cc @jkotas @richlander @clairernovotny

@AaronRobinsonMSFT AaronRobinsonMSFT added the enhancement New feature or request label Jun 10, 2020
@Scottj1s
Copy link
Member

How common are these scenarios? Are we referring only to app projects? Otherwise, how could a library project know whether it would ever be combined with other winrt code downstream? If a project is prepared to generate and compile the entirety of the cswinrt projection and runtime sources, could it not just as easily use il linking to eliminate the transitive winrt.runtime.dll dependency? This seems like a lot of work for a narrow speculative case.

@clairernovotny
Copy link
Member

clairernovotny commented Jun 10, 2020

I expect these to be the most common case and should be the defaults for all libraries. The .NET Tool chain does not normally use IL Linking as standard practice for libraries and IL Linking currently interferes with reproducible builds.

IL Linking and trimming would be done at the application level on publish.

@Scottj1s
Copy link
Member

The greater concern is that this would make all such libraries mutually incompatible. The winrt.runtime.dll exists to provide single definitions for RCW/CCW caches, projected types, etc. A similar justification exists for the Windows SDK and WinUI interop dlls produced from cswinrt - these are effectively PIAs. The Windows/WinUI projections could be generated by each app (and the main readme.md page here allows for that as a fallback), but the consequence is conflicting definitions of projected types.

@clairernovotny
Copy link
Member

clairernovotny commented Jun 10, 2020

PIA's can be, and are, embedded in .NET to avoid these issues. That has been the recommendation since .NET 4 https://docs.microsoft.com/en-us/dotnet/framework/interop/deploying-an-interop-application

@jkoritzinsky
Copy link
Member

jkoritzinsky commented Jun 10, 2020

PIAs can rely in the runtime's support for Type Equivalence to avoid the type identity problem. The type equivalence algorithm does not cover all possible cases of the WinRT type system, so C#/WinRT can't rely on it without some harsh corners where support would just drop off. @AaronRobinsonMSFT would know more about the specifics of the limitations of Type Equivalence.

@clairernovotny
Copy link
Member

Understood, and that's what we need to solve.

@AaronRobinsonMSFT
Copy link
Member Author

PIA's can be, and are, embedded in .NET to avoid these issues.

PIA's have never worked for WinRT because of generic classes. As @jkoritzinsky mentioned Type Equivalence is the underlying technology that enables PIA and that is limited to COM scenarios and some small cut outs for other scenarios. Changing that feature to support WinRT would be substantial and has been discussed before - it is unlikely to happen.

@Scottj1s What about the fully embedded scenario for cases where the WinRT use is entirely internal? That seems like a reasonable scenario to support, no?

@Scottj1s
Copy link
Member

That scenario sounds like it's covered by #78, and only at app scope, yes? I.e., how can a library author be certain that theirs is the sole winrt-based module in any consuming process? The moment it's combined with any other winrt-based lib, we have the potential for compat issues. And these would be very easy to trip over - even two libs with modest, consume-only use of winrt APIs could have issues around RCW identity, etc.

@jkoritzinsky
Copy link
Member

jkoritzinsky commented Jun 10, 2020

With a fix to dotnet/runtime#37492 with a corresponding fix to the WeakReference support to consume it, we should be able to come up with a design that says "if you don't use WinUI, you can have multiple isolated WinRT-based modules that work together as long as they never exchange objects" (Support for reference tracking makes the WinUI scenario significantly more difficult to safely reason about). A fix to the above issue should fix the RCW identity problem since each embedded system would be completely self-contained and neither system would have any way of knowing that there's another RCW for the same object since they never interact.

@weltkante
Copy link

weltkante commented Jun 10, 2020

The winrt.runtime.dll exists to provide single definitions for RCW/CCW caches

What about custom winmd RCWs that are not part of Windows? Like user defined controls programmed in C++? We fully expect having to consume third party winmd files as well as providing custom projections for classic Windows COM. I hope that winrt.runtime.dll is "open enough" to live alongside third party projections, otherwise you are building a recipe for failure, there never can be a single source of truth for interop definitions.

@weltkante
Copy link

weltkante commented Jun 10, 2020

Also, what about the "triangle dependency" problem?

  • third party C++ custom control library (A) - so can't define the RCW via this projects nuget package
  • two distinct libraries (B1) and (B2) both working with (A)
  • application (C) requiring both (B1) and (B2) to work with the same control (A)

If referencing a WinRT control requires generating an RCW outside the runtime in (B1) and (B2) you better have some strategy for different RCWs to interop

Similar problems may occur with CCWs.

@jkoritzinsky
Copy link
Member

WinRT.Runtime only includes projections for type mappings, so it should be able to live alongside projections of 3rd party components since the third party components would depend on WinRT.Runtime (or be generated with an embedded runtime once one exists).

For the triangle dependency problem in the non-embedded scenario: the owner of the C++ custom control library is required to produce the interop package and ship it themselves. That way there is one source of truth for the RCW definition.

@jkoritzinsky
Copy link
Member

jkoritzinsky commented Jun 11, 2020

For the embedded scenario, here's a quick writeup of the proposal discussed over email:

Provide a Microsoft.CsWinRT.Runtime.Embedded.Sources package that has cswinrt.exe, a copy of the WinRT.Runtime sources all marked as internal with modifications to not register the ComWrappers instance globally, and a file that includes an attribute, [assembly:IncludesEmbeddedWinRTProjection]. When the WinRT.Runtime code tries to look up a type from its WinRT name, it will first check if the assembly it is in has this attribute. If it does, it will only search this assembly and the (local copy of the) type mapping registrations for the type. If it doesn't have the attribute, it will search every loaded assembly (as is the current behavior today) except assemblies with the IncludesEmbeddedWinRTProjectionAttribute attribute. As a result, embedded WinRT projections will only know about the types that were projected with them, and the global projections will be discovered correctly by the global ComWrappers instance. An important note here is we need to match the attribute on name, not on type since each embedded projection will have its own definition of the type.

@BenJKuhn
Copy link
Member

BenJKuhn commented Aug 7, 2020

Clearing the milestone on this based on current timeline & resourcing.

@AdamBraden AdamBraden added this to the Release 1.1 milestone Sep 14, 2020
@dotMorten
Copy link

With .NET Core 3.1, I was able to use reflection to detect access to WinRT APIs and light them up if I'm running on Windows. This was great because I relied on WinRT APIs available since Windows 8.0.
With .NET5, reflection to WinRT is no longer supported, and the only way to call WinRT APIs are by targeting net5-windows10.0.17763. This works great for building windows apps, but it works pretty poorly for building cross-platform .NET apps, or building class libraries. Sure I can multi-target, but I'd prefer just targeting net5, because if a user is targeting net5.0 or even net5.0-windows, it'll fallback to the netcoreapp3.1 assembly which is likely still using reflection into WinRT APIs and will fail on .NET 5. I covered some of that here: #458

My thought was to build my own WinRT Projection and embed into my assembly, and throw some try/catch and OS checks around it, so I that way would detect support for those APIs and use them. However I didn't have much luck with that.

So I made the following project to get access to GeoLocation APIs, and compiled it:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CsWinRT" Version="0.8.0" PrivateAssets="All" />
  </ItemGroup>
  <PropertyGroup>
    <CsWinRTIncludes>
      Windows.Devices.Geolocation;
      Windows.Foundation.Metadata.ContractVersion;
      Windows.Foundation.UniversalApiContract;
      Windows.Foundation.Metadata.DualApiPartition;
      Windows.Foundation.Metadata.MarshalingBehavior;
      Windows.Foundation.Metadata.MarshalingType;
      Windows.Foundation.Metadata.Activatable;
      Windows.Foundation.Metadata.Static;
      Windows.Foundation.Metadata.Threading;
      Windows.Foundation.Metadata.ThreadingModel;
      Windows.Foundation.Metadata.Muse;
      Windows.Foundation.Metadata.ExclusiveTo;
      Windows.Foundation.Metadata.ApiContract;
      Windows.Foundation.Metadata.AttributeName;
      Windows.Foundation.Metadata.AllowMultiple;
      Windows.Foundation.IAsyncOperation;
      Windows.Foundation.TypedEventHandler;
      Windows.Foundation.UniversalApiContract;
      Windows.Foundation.FoundationContract;
      Windows.Foundation.IAsyncInfo;
      Windows.Foundation.IAsyncAction;
      Windows.Foundation.AsyncStatus;
      Windows.Foundation.AsyncActionCompletedHandler;
      Windows.Foundation.AsyncActionProgressHandler;
      Windows.Foundation.AsyncActionWithProgressCompletedHandler;
      Windows.Foundation.AsyncOperationProgressHandler;
      Windows.Foundation.AsyncOperationCompletedHandler;
      Windows.Foundation.AsyncOperationWithProgressCompletedHandler;
    </CsWinRTIncludes>
    <CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
    <CsWinRTWindowsMetadata>sdk</CsWinRTWindowsMetadata>
    <CsWinRTGenerateProjection>true</CsWinRTGenerateProjection>
  </PropertyGroup>
</Project>

However, because it generates public classes, but the WinRT.cs and WinRT_Interop.cs files have all types internal (but those types are exposed in the generated classes) I get the following build errors:,

`>\obj\Debug\net5.0\Generated Files\Windows.Foundation.Metadata.cs(30,16,30,36): error CS0051: Inconsistent accessibility: parameter type 'Platform' is less accessible than method 'ActivatableAttribute.ActivatableAttribute(uint, Platform)'
1>\obj\Debug\net5.0\Generated Files\Windows.Foundation.Metadata.cs(33,16,33,36): error CS0051: Inconsistent accessibility: parameter type 'Platform' is less accessible than method 'ActivatableAttribute.ActivatableAttribute(Type, uint, Platform)'
1>\obj\Debug\net5.0\Generated Files\Windows.Foundation.Metadata.cs(114,16,114,31): error CS0051: Inconsistent accessibility: parameter type 'Platform' is less accessible than method 'StaticAttribute.StaticAttribute(Type, uint, Platform)'

My main problem in the first place is that it generates public classes. I want them all to be internal, as I don't actually want to expose these methods, (doesn't seem like the source generator has that option) but just use them internally in a light-up way. There's just no reason to embed WinRT types and expose them in your class library - if you needed that in your app, just target the TFM that include WinRT APIs.

I then took the generated code from the obj folder and embedded them in a class library, and changed all public classes to internal. Now things actually do compile, but sadly I'm getting bad crashes at runtime, and in addition I'm forced to deploy a WinRT dependency as well (Microsoft.Windows.CsWinRT).

            WinRT.ComWrappersSupport.RegisterProjectionAssembly(typeof(Program).Assembly);
            Geolocator g = new Geolocator(); //OK
            g.DesiredAccuracy = PositionAccuracy.Default; // BOOM!

image

@tthiery
Copy link

tthiery commented Apr 17, 2021

I have the use case of "the usage of the WinRT API is entirely internal to the library". I abstract the Bluetooth LE Stack (I have four implementations in the project) and WinRT is one of the implementations. Currently the TFM net5.0-windows.... of .NET is forcing itself into the application domain which I would prefer to have a single TFM (net5.0). I tried an AssemblyLoadContext Plugin approach which worked, however, the consequent packaging is just an unneeded hassle.

@angelazhangmsft
Copy link
Contributor

Embedded support is now available with C#/WinRT v1.4.1! These docs and samples provide more details on how to use the support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
12 participants