microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.83k stars 321 forks source link

[Dynamic Dependencies] WinUI 2 fails to load resources #3242

Open dongle-the-gadget opened 1 year ago

dongle-the-gadget commented 1 year ago

Describe the bug

When using Windows App SDK Dynamic Dependencies (MddTryCreatePackageDependency and MddAddPackageDependency), WinUI 2 isn't able to load the appropriate resources.

onecoreuap\base\mrt\runtime\com\dllsrv\mrtresourcemanager.cpp(228)\MrmCoreR.dll!00007FF86B9A2009: (caller: 00007FF869CC9435) ReturnHr(22) tid(c0) 80073B1F ResourceMap Not Found.
Exception thrown at 0x00007FF87DC1E1FC (KernelBase.dll) in XamlIslandsApplication.exe: WinRT originate error - 0x80004005 : 'Cannot locate resource from 'ms-appx://Microsoft.UI.Xaml.2.7/Microsoft.UI.Xaml/Themes/21h1_themeresources.xaml'.'.

Steps to reproduce the bug

Problematic code:

void RegisterPackageDependency(LPCWSTR packageFamilyName, PACKAGE_VERSION minVersion)
{
    MddPackageDependencyProcessorArchitectures arch;
#if defined(_M_X64)
    arch = MddPackageDependencyProcessorArchitectures::X64;
#elif defined(_M_ARM64)
    arch = MddPackageDependencyProcessorArchitectures::Arm64;
#elif defined(_M_IX86)
    arch = MddPackageDependencyProcessorArchitectures::X86;
#endif

    PWSTR dependencyId, discardedPfn;
    MDD_PACKAGEDEPENDENCY_CONTEXT discardedContext;
    MddTryCreatePackageDependency(NULL, packageFamilyName, minVersion, arch, MddPackageDependencyLifetimeKind::Process, NULL, MddCreatePackageDependencyOptions::None, &dependencyId);
    // Give a higher rank so that our packages are prioritized over Windows App SDK.
    MddAddPackageDependency(dependencyId, -1, MddAddPackageDependencyOptions::None, &discardedContext, &discardedPfn);
}

MddBootstrapInitialize(::Microsoft::WindowsAppSDK::Release::MajorMinor, ::Microsoft::WindowsAppSDK::Release::VersionTag, PACKAGE_VERSION{});

RegisterPackageDependency(L"Microsoft.VCLibs.140.00_8wekyb3d8bbwe", PACKAGE_VERSION{
    .Revision = 0,
    .Build = 30704,
    .Minor = 0,
    .Major = 14
});

RegisterPackageDependency(L"Microsoft.UI.Xaml.2.7_8wekyb3d8bbwe", PACKAGE_VERSION{
    .Revision = 0,
    .Build = 13004,
    .Minor = 2109,
    .Major = 7
});

xamlApp = ::winrt::Microsoft::Toolkit::Win32::UI::XamlHost::XamlApplication({ winrt::Microsoft::UI::Xaml::XamlTypeInfo::XamlControlsXamlMetaDataProvider() });
auto test = winrt::Microsoft::UI::Xaml::Controls::XamlControlsResources{}; // fails here

Project configuration:

  1. Requires Microsoft.UI.Xaml 2.7, Microsoft.WindowsAppSDK and Microsoft.Toolkit.Win32.UI.XamlApplication
  2. Add these lines to a PropertyGroup:
    <BeforeLinkTargets>
      $(BeforeLinkTargets);
      _UnpackagedWin32GenerateAdditionalWinmdManifests;
    </BeforeLinkTargets>
  3. Add these targets
    <Target Name="RemoveWinUI3" BeforeTargets="ResolveAssemblyReferences">
      <ItemGroup>
        <_WinUI3Reference Include="@(Reference)" Condition="'%(Filename)' == 'Microsoft.WinUI' OR ($([System.String]::Copy('%(FullPath)').ToLowerInvariant().Contains('windowsappsdk')) AND %(Filename) == 'Microsoft.UI.Xaml')" />
        <Reference Remove="@(_WinUI3Reference)" />
        <ReferenceCopyLocalPaths Remove="@(ReferenceCopyLocalPaths)" Condition="'%(Filename)' == 'Microsoft.WinUI' OR ($([System.String]::Copy('%(FullPath)').ToLowerInvariant().Contains('windowsappsdk')) AND %(Filename) == 'Microsoft.UI.Xaml')" />
      </ItemGroup>
    </Target>
    <Target Name="_UnpackagedWin32MapWinmdsToManifestFiles" DependsOnTargets="ResolveAssemblyReferences">
      <ItemGroup>
        <!-- For each non-system .winmd file in References, generate a .manifest in IntDir for it. -->
        <_UnpackagedWin32WinmdManifest Include="@(ReferencePath->'$(IntDir)\%(FileName).manifest')" Condition="'%(ReferencePath.IsSystemReference)' != 'true' and '%(ReferencePath.WinMDFile)' == 'true' and '%(ReferencePath.ReferenceSourceTarget)' == 'ResolveAssemblyReference' and '%(ReferencePath.Implementation)' != ''">
          <WinMDPath>%(ReferencePath.FullPath)</WinMDPath>
          <Implementation>%(ReferencePath.Implementation)</Implementation>
        </_UnpackagedWin32WinmdManifest>
        <!-- For each referenced project that _produces_ a winmd, generate a temporary item that maps to
           the winmd, and use that temporary item to generate a .manifest in IntDir for it.
           We don't set Implementation here because it's inherited from the _ResolvedNativeProjectReferencePaths. -->
        <_UnpackagedWin32WinmdProjectReference Condition="'%(_ResolvedNativeProjectReferencePaths.ProjectType)' != 'StaticLibrary'" Include="@(_ResolvedNativeProjectReferencePaths-&gt;WithMetadataValue('FileType','winmd')-&gt;'%(RootDir)%(Directory)%(TargetPath)')" />
        <_UnpackagedWin32WinmdManifest Include="@(_UnpackagedWin32WinmdProjectReference->'$(IntDir)\%(FileName).manifest')">
          <WinMDPath>%(Identity)</WinMDPath>
        </_UnpackagedWin32WinmdManifest>
      </ItemGroup>
    </Target>
    <Target Name="_UnpackagedWin32GenerateAdditionalWinmdManifests" Inputs="@(_UnpackagedWin32WinmdManifest.WinMDPath)" Outputs="@(_UnpackagedWin32WinmdManifest)" DependsOnTargets="_UnpackagedWin32MapWinmdsToManifestFiles">
      <Message Text="Generating manifest for %(_UnpackagedWin32WinmdManifest.WinMDPath)" Importance="High" />
      <!-- This target is batched and a new Exec is spawned for each entry in _UnpackagedWin32WinmdManifest. -->
      <Exec Command="mt.exe -winmd:%(_UnpackagedWin32WinmdManifest.WinMDPath) -dll:%(_UnpackagedWin32WinmdManifest.Implementation) -out:%(_UnpackagedWin32WinmdManifest.Identity) -nologo" />
      <ItemGroup>
        <!-- Emit the generated manifest into the Link inputs. Pass a metadata name that isn't used to wipe all 
            metadata because otherwise VS tries copying the manifest to the output as %(FileName).winmd. -->
        <Manifest Include="@(_UnpackagedWin32WinmdManifest)" KeepMetadata="DoesntExist" />
      </ItemGroup>
    </Target>

Expected behavior

WinUI 2 should be able to find the resources.

Screenshots

No response

NuGet package version

Windows App SDK 1.2.1: 1.2.221116.1

Packaging type

Unpackaged

Windows version

Insider Build (xxxxx)

IDE

Visual Studio 2022-preview

Additional context

Using Windows 11 Dynamic Dependencies (TryCreatePackageDependency and AddPackageDependency) doesn't cause this problem.

DrusTheAxe commented 1 year ago

@MikeHillberg MRT searches the package graph for resources, but there's some WinUI added behavior in resource resolution. Where's the resource resolution doc'd?

dongle-the-gadget commented 1 year ago

Oh, so this is a problem with the system provided MRT?

DrusTheAxe commented 1 year ago

Yes, or system MRT, WinUI2 and/or WinAppSDK Dynamic Dependencies mixing in some fashion the dots don't quite line up. The source path is the tipoff it's MRTSystem (aka MRT) rather than WinAppSDK's MRTCore -- onecore* are root dirs in Windows' source tree.

An unpackaged app using WinUI2 + MRT(System) via WinAppSDK Dynamic Dependencies. Yeah, that needs a deep WinUI expert. I jus know enough to know we need one :-)

evelynwu-msft commented 1 year ago

I suspect this is a gap in System MRT vis a vis WinAppSDK Dynamic Dependencies; WinUI 2 (and UWP XAML) has no say in System MRT's initialization or its search pattern.

dongle-the-gadget commented 1 year ago

Do you know if Windows 11 Dynamic Dependencies influence MRT's search pattern?

DrusTheAxe commented 1 year ago

Do you know if Windows 11 Dynamic Dependencies influence MRT's search pattern?

Yes. MRT's search is based on the process' package graph. Dynamic Dependencies key role is to add packages to the package graph at runtime.

Dynamic changes could be unnoticed if folks cache the package graph. COM used to do this - call GetCurrentPackageInfo() once and cache the result in a global variable. This was changed to account for dynamic dependencies (caching was removed - wasn't such a win as originally presumed). MRT never did any such caching but if WinUI's doing any such caching and not accounting for cache invalidation...

That's why MddGetPackageGraphRevisionId() (WinAppSDK/Flat-C), Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency.PackageGraphRevisionId (WinAppSDK/WinRT) and GetPackageGraphRevisionId() (Win11) exist. Those who cache package graph (directly or indirectly) can use these APIs to determine if if's changed since the last time they looked and invalidate/recompute their cache'd data.

NOTE: GetPackageGraphRevisionId() is new in the latest version of Windows. There's a GetCurrentPackageInfo3() one can use on older systems

@MikeHillberg @bpulliam would know if WinUI's caching the package graph or otherwise altering the MRT's behavior

dongle-the-gadget commented 1 year ago

Any progress?

seven-mile commented 1 year ago

Oh, I got this error, too. Never thought someone had encountered it, interesting. Any progress on this?