microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.36k stars 678 forks source link

Component Xaml file missing from app directory #9633

Closed RobertHF closed 5 months ago

RobertHF commented 6 months ago

I'm finding that if I create a WinUI 3 C++ runtime component (Visual Studio 2022 Community) and add a Xaml class to it (e.g. a user control), and then try to use the component from a WinUI 3 C++ app, the app is looking for the user control's xaml file in a subdirectory of Appx, but that subdirectory isn't being created, and the application fails at startup. The app and component are in separate projects in separate directories, and the app references the component via its .winmd file. The app can be made to run by manually creating the missing subdirectory.

I've created two very simple projects which can be downloaded here: https://1drv.ms/u/s!ArdAaIuUWNACgow6LaB7XGJAf34QiA?e=pcwSJm

Also, just in case I'm missing a step, the steps I took to create the projects are:

  1. Create a C++ Windows Runtime Component (WinUI 3). Name the project Comp1, and place solution and project in same directory. Keep default target and minimum version.

  2. Select Add new Item and add a WinUI user control. Name it UsrCtrl1.xaml, then build the project.

  3. In a separate directory, create a C++ Blank App, Packaged (WinUI 3 in desktop) project. Name the project App1, and place solution and project in same directory.

  4. Right click App1 project node - Add Reference, browse to the x64/Debug/Comp1 directory under the Comp1 project folder, and add a reference to Comp1.winmd.

  5. Add #include <winrt/Comp1.h> to the App1 pch.h file

  6. in App1's MainWindow.xaml file, add the namespace identifier xmlns:local2="using:Comp1" . Comment out the button xaml element and add a UsrCtrl1 element: <local2:UsrCtrl1> </local2:UsrCtrl1> . Comment out the line referring to myButton in MainWindow.xaml.cpp.

  7. Build the app and run it. The app fails on startup. Debugging, it appears to fail while looking for AbsoluteUri = L"ms appx:///Comp1/UsrCtrl1.xaml", but Appx has no Comp1 subdirectory.

  8. Copy the \x64\Debug\Comp1\Comp1 directory from the Comp1 project to App1's x64\Debug\App1\AppX directory. This folder contains two files: UsCtrl1.xaml and UsrCtrl1.xbf. The app now runs successfully.

I could create the missing directory using a post-build command, but I'd like to try and understand why this is happening.

github-actions[bot] commented 6 months ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

DarranRowe commented 6 months ago

I think the big issue here is that, surprisingly(?), referencing the .winmd file only tells the build system what WinRT components are available. It doesn't tell the build system where auxiliary files are. Also, even if you manually copied the files, the build system is unaware of resources in general. This means that if you added resources to the component, then they would be missing since the component would be looking in your application's main .pri file.

To use components in an application, there are generally two ways of doing it.

The simpler way is to have the component as a project in your solution.

Screenshot 2024-05-14 153803

Using this layout, all you have to do is set the component as a project reference to your main executable.

Screenshot 2024-05-14 153825

The project reference allows the build system to know the locations of the project output and auxiliary files. This also merges any resources that the component uses into the main executable's resources file.

The second method is actually more of a pain, and it relies on making your component into a framework package and referencing that. This method allows you to only reference the .winmd file since everything else should then be read from the component's package directory. One thing that makes this a pain is, if your application is an unpackaged desktop application, you need to use the Dynamic Dependencies API to access it. Unless something has changed within the last release or two, only the Windows 11 Dynamic Dependencies will work with this. The Windows App SDK Dynamic Dependencies won't be able to find the resources from another package. If your application is packaged, then setting it as a package reference is good enough since Windows will take care of everything correctly.

You cannot load the component from outside of the application directory without it being part of a framework package.

RobertHF commented 6 months ago

@DarranRowe -Thanks for that. The reason I posted the question is that what I really want to do is create a WinUI 3 C++ component and use that from a C# WinUI 3 app. This allows me to render to a control using DirectX in the component, and write the app user interface in C#. I had been following this walkthrough: https://learn.microsoft.com/en-us/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component where they describe how to use CsWinRT: https://github.com/microsoft/CsWinRT to generate a C# interop dll for a C++ component. With this approach you need to package the component and interop assembly in a nuget package, so when the app is included, you're dealing with three projects rather than two. They recommend not putting the app in the same solution as the component and intreop assembly, but I don't know if its essential. When I built the component in this setup with a xaml class just to try it, I found that the app couldn't find the xaml file, so I created the C++ app/component test projects to see more clearly what was happening.

Luckily, the C++ component I want to create probably doesn't need xaml. All I need in the component is a control with a background property that I can render to with DirectX via SurfaceImageSource. I did this in a Windows 8.1 app I previously had in the Windows Store, and I'm having a go at porting it to WinUI 3.

Thanks again. The fact that Xaml files aren't handled automatically still sounds like a shortcoming to me though. Would it be possible to add Xaml files to a nuget package and somehow tell the app where to find them? Also, you mention creating a packaged component. I'm not going to do that but I'd be interested to see how its done. Do you know of any documentation on it?

DarranRowe commented 6 months ago

If you are packaging it up in a NuGet package, then you will be adding a .targets file and a .props file to the package to let the build system know how to handle your package. Since these are MSBuild files, then you can use them to do everything you want. The interesting thing to note about this is, for Release builds, the .xbf files (which are the binary .xaml files) are embedded into the .pri file. This means that you normally only have to add a reference to that. Even though it is pretty big and difficult to wade through, the Windows App SDK NuGet package does what you want when it is building a project set to make the Windows App SDK self contained.

For the packaged component, I don't think it is really documented well, but there is a pretty easy to obtain reference in the Windows App SDK/Windows App Runtime .msix package. The important thing is that it is set as a framework package.

evelynwu-msft commented 5 months ago

Closing this issue as @DarranRowe has done a good job (thanks!) of explaining the steps that should be followed to enable this scenario.