CsWinRT icon indicating copy to clipboard operation
CsWinRT copied to clipboard

Unable to run .NET Standard 2.0 binaries with embedded mode on .NET Framework 4.8

Open PDeets opened this issue 1 year ago • 3 comments

Describe the bug I have an existing library which used built-in support for WinRT. This library is used by both .NET Framework 4.x and .NET Core. In order to add support for .NET 5 and newer consumers of this library, I adopted C#/WinRT's embedded mode with the .NET Standard 2.0 target. However, when I tested the binary on .NET Framework 4.8, I got this exception:

System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. File name: 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
at WinRT.MarshalString.Pinnable.GetAbi()
at WinRT.WinrtModule.GetActivationFactory[I](String runtimeClassId, Guid iid)
at WinRT.ActivationFactory.Get[I](String typeName, Guid iid)
...

The code in Marshallers.cs is using System.Runtime.CompilerServices.Unsafe. Although this is included in .NET Standard 2.0, it is not in .NET Framework 4.8. I'm guessing this is a bug in what types are in .NET Standard 2.0. C#/WinRT's embedded mode should work with .NET Framework 4.8 when using the .NET Standard 2.0 target framework.

If this is not intended to be supported, the documentation should be updated to state this. In https://github.com/microsoft/CsWinRT/blob/master/docs/embedded.md, it states: "App consumers are able to target any .NET Standard 2.0 compatible TFM". .NET Framework 4.8 is .NET Standard 2.0 compatible according to https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0.

Multi-targetting isn't a good option for me as the existing deployment mechanism expects a single set of binaries to work with all consumers.

To Reproduce Use C#/WinRT embedded mode with a target framework of netstandard2.0 and try to run the assembly in a .NET Framework 4.8 process.

Expected behavior WinRT APIs should work.

Version Info CsWinRT NuGet: 2.0.8 Visual Studio version: 17.11.0

PDeets avatar Aug 20 '24 03:08 PDeets

@PDeets, can you add a reference to the System.Runtime.CompilerServices.Unsafe package? In the version I referenced, it does have a .NET Framework version that matches the assembly version that it is looking for.

manodasanW avatar Aug 21 '24 01:08 manodasanW

It is good to know about that option. However, a big appeal of .NET Standard 2.0 embedded mode for me was that I thought my binary wouldn't take on any additional dependencies. The deployment system that deploys my binaries is not a .NET-specific tool. It has no concept of whether the consumer of my binaries is going to use .NET Framework, .NET Core, or .NET. Code that depends on my library just points to the deployment package to have my binaries be deployed regardless of their target framework. I don't have a mechanism to have a separate set of files for .NET Framework and .NET Core users of the library. If these forked, I'd have to provide separate packages for the .NET Framework version vs. the .NET Core verison and manually update each consumer to deploy the correct one.

With .NET Core 2.1, I ran into a similar issue where it failed due to System.Numerics.Vectors.dll being missing while the type initializer for the WinRT.Projections class depended on it. (I know .NET Core 2.1 is out of support, but the work I'm doing to adopt C#/WinRT will help to enable us to migrate away from it to supported versions of .NET.) These extra dependencies that embedded mode takes on are making it very difficult for me to adopt when migrating from built-in support for WinRT where no extra assemblies are needed. When using .NET 6, everything works without any extra binaries needed that are not part of the .NET distribution.

If I have .NET Framework-specific dependencies, I think I'm going to have to copy the code generated by embedded mode, make it part of my project, and tweak the generated code to eliminate the dependency. That way I can continue to distribute my binaries without taking on target-framework-specific dependencies.

PDeets avatar Aug 21 '24 22:08 PDeets

This was a blocker for me, so I ended up abandoning C#/WinRT for my project. Instead, I wrote my own custom code to do the interop. I didn't need to implement any interfaces or use any non-agile interfaces. Also most of the methods I needed to call were static methods, which means they are on interfaces that could be retrieved using RoGetActivationFactory. This made it so it wasn't that difficult to write my own code for this that worked on .NET Framework, .NET Core 2.1, and .NET 6 without requiring any binary dependencies.

PDeets avatar Aug 23 '24 19:08 PDeets