Search code examples
c#wpfwindows-runtimec++-winrt

Use C++/WinRT component (but really any WinRT component) in WPF .NET 6


I would like to write a WPF application, but build it on modern frameworks as much as possible, including for example .NET 6 or maybe even higher.

I have some C++ code that I want to call, and I like WinRT as a component model instead of low level P/Invoke.

I already have managed to integrate my C++ code into a WinUI3 app using CsWinRT. Now I try to do the same with my WPF app. My attempt can be seen at https://github.com/mmarczell-graphisoft/winrtbug

As long as I do not have the using directive in my WPF C# code, nor any code using those definitions, the C# projection file does get generated under obj\Debug\net6.0-windows\Generated Files\CsWinRT. However as soon as I enter the using directive and rebuild, this file is no longer there.

What am I doing wrong?


I have found some documentation about similar usecases, but they are some years old and do not seem not directly applicable.

Originally, it was not supported to use WinRT components in non packaged apps. https://github.com/microsoft/cppwinrt/issues/256

Then in 2019 "Registration-free WinRT" was announced: https://blogs.windows.com/windowsdeveloper/2019/04/30/enhancing-non-packaged-desktop-apps-using-windows-runtime-components/

with sample code: https://github.com/microsoft/RegFree_WinRT

However with .NET 5, the ability to directly reference a .winmd was removed from .NET and extracted into the CsWinRT package: https://github.com/microsoft/CsWinRT

and that is what I'm failing to configure correctly. The documentation states that direct project references are not possible, and instead a two phase build process should be used, with the first phase packaging the library as a NuGet package, and the top level application referencing that. https://learn.microsoft.com/en-us/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component

However MS employees in GitHub discussions are repeatedly suggesting that this is in fact unnecessary, as CsWinRT supports a simpler workflow by directly referencing the projects, for example at https://github.com/microsoft/CsWinRT/discussions/1072#discussioncomment-8497693 and https://github.com/microsoft/CsWinRT/issues/1289#issuecomment-1481878399

I have also submitted this question as a GitHub issue: https://github.com/microsoft/CsWinRT/issues/1543


Solution

  • Many articles pointed out here are old (aka few years, but few years are ages with .NET Core), and with recent Framework some things are easier than they were but it's hard to find the solution.

    So, starting from this (official) tutorial Generate a C# projection from a C++/WinRT component, distribute as a NuGet for .NET apps wich is available as sample code here C#/WinRT Projection Sample. First thing to do is update all packages, especially CsWinRT which is completely outdated (current version as of today is 2.0.7).

    And then, thanks to this newer CsWinRT, you don't even need the use an intermediary Nuget project like what's explained in the sample, because this new CsWinRT generates some .cs file on the fly for us from the component's .winmd file (metadata).

    Just build the SimpleMathComponent C++/WinRT project, and now you can reference it in a console app with a .csproj like this, using .NET 8 and x64 (debug or release)

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
        <Nullable>enable</Nullable>
        <PlatformTarget>x64</PlatformTarget>
        <Platforms>x64</Platforms>
    </PropertyGroup>
    
    <ItemGroup>
        <PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.7" />
    </ItemGroup>
    
    <ItemGroup>
        <ProjectReference Include="..\SimpleMathComponent\SimpleMathComponent.vcxproj" />
    </ItemGroup>
    
    <PropertyGroup>
        <CsWinRTIncludes>
            SimpleMathComponent;
        </CsWinRTIncludes>
    </PropertyGroup>
    

    We can see the .cs projection auto-generated file in the obj directory:

    enter image description here

    Now, with a WPF project, the same project layout doesn't work. For some reason, the SimpleMathComponent component is not picked up by CsWinRT for code generation (the only real difference I can see is OutputType, Exe vs WinExe) and the SimpleMathComponent.cs is not generated which causes a compilation error. Looks like a bug to me.

    A possible workaround is to feed CsWinRT directly with the input it needs, something like this;

    <Project Sdk="Microsoft.NET.Sdk">
    
        <PropertyGroup>
            <OutputType>WinExe</OutputType>
            <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
            <Nullable>enable</Nullable>
            <PlatformTarget>x64</PlatformTarget>
            <Platforms>x64</Platforms>
            <UseWPF>true</UseWPF>
        </PropertyGroup>
    
        <ItemGroup>
            <PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.7" />
        </ItemGroup>
    
        <ItemGroup>
            <ProjectReference Include="..\SimpleMathComponent\SimpleMathComponent.vcxproj" />
        </ItemGroup>
    
        <PropertyGroup>
            <CsWinRTParams>
                -target net6.0
                -input 10.0.19041.0
                -input "$(BuildOutDir)SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.winmd"
                -output "$(IntermediateOutputPath)\net8.0-windows10.0.19041.0\Generated Files\CsWinRT"
                -exclude Windows
                -exclude Microsoft
                -include SimpleMathComponent
            </CsWinRTParams>
        </PropertyGroup>
    </Project>
    

    Obviously it's less practical than the Console app version (and it needs to be adapted when varying .NET versions, SDK versions, etc.).