为什么修改项目输出目录会导致:IOException未处理&;无法定位资源'app.xaml';

本文关键字:资源 xaml app 未处理 定位 IOException 输出 项目 修改 为什么 | 更新日期: 2023-09-27 18:18:24

为了尝试将项目设置合并到c++和c#项目的属性表中,构造了以下属性表:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!--
      Trying to support both C++ and C# projects by introducing derived 
      properties and setting the appropriate output properties.
  -->
  <PropertyGroup Label="UserMacros">
    <ProjectOrAssemblyName Condition="'$(AssemblyName)'==''">$(ProjectName)</ProjectOrAssemblyName>
    <ProjectOrAssemblyName Condition="'$(ProjectName)'==''">$(AssemblyName)</ProjectOrAssemblyName>
    <ShortPlatform Condition="'$(Platform)'=='Win32'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)'=='x86'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)'=='x64'">x64</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)'=='AnyCPU'">AnyCPU</ShortPlatform>
  </PropertyGroup>
  <PropertyGroup>
    <OutputPath>$(OutputRelativePath)/$(ProjectOrAssemblyName)_$(ShortPlatform)_$(Configuration)/</OutputPath>        
    <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(ProjectOrAssemblyName)_$(ShortPlatform)</BaseIntermediateOutputPath>
    <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)/</IntermediateOutputPath>
    <IntDir>$(IntermediateOutputPath)</IntDir>
    <OutDir>$(OutputPath)</OutDir>
  </PropertyGroup>
</Project>

此属性表将把所有构建输出移动到包含源代码的目录之外的单独位置OutputRelativePath(在单独的属性表中定义或直接在项目文件中定义),以便更容易清理等。然而,在设置好之后,构建工作正常,所有单元测试工作正常,很明显,WPF可执行项目并不是很好,因为使用上面的属性表运行应用程序会导致臭名昭著的:

IOException was unhandled "Cannot locate resource 'app.xaml'."

为什么改变输出路径会导致这个错误?如何确定原因是项目构建输出路径?这可以在生成的代码中看到吗?我找不到吗?这不是臭虫吗?

注意:使用下面的属性表工作,但只有当IntermediateOutputPath包含BaseIntermediateOutputPath

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)</OutputPath>
    <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
    <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)</IntermediateOutputPath>
  </PropertyGroup>
</Project>

因此,似乎期望输出路径包含AssemblyName属性或类似属性。

在另一个程序集中更新XAML样式:同样适用于XAML ResourceDictionary,如果这些-例如画笔。xaml -位于另一个程序集中,并且该程序集也更改了OutputPath,这也会抛出异常:

XamlParseException was unhandled for set property Source 
with InnerException "Cannot locate resource 'Brushes.xaml'" 

所以总而言之,输出位置改变了xaml资源名,所以在运行时无法发现这些。奇怪的是,这在设计时并不是一个问题……


UPDATE:复制异常的最小步骤:

Open Visual Studio 2013

创建新的c#项目WPF应用程序,例如XamlIntermediateOutputPathBug

卸载项目

编辑项目文件

在第一个PropertyGroup之后插入新的PropertyGroup作为:

<PropertyGroup>
  <OutputRelativePath>$(ProjectDir)..'Build</OutputRelativePath>
  <OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)/</OutputPath>
  <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
  <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)/</IntermediateOutputPath>
  <IntDir>$(IntermediateOutputPath)</IntDir>
  <OutDir>$(OutputPath)</OutDir>
</PropertyGroup>  

删除剩余PropertyGroups中的OutputPath属性,例如

<OutputPath>bin'Debug'</OutputPath>  

:

<OutputPath>bin'Release'</OutputPath>  

这应该在启动mainwindow.xaml时抛出一个IOException。这是因为$(AssemblyName).g.resources嵌入的资源被赋予了以下名称:

.mresource public 'Build/Obj_Exe/XamlIntermediateOutputPathBug_AnyCPU_Debug/XamlIntermediateOutputPathBug.g.resources' as Build_Obj_Exe_XamlIntermediateOutputPathBug_AnyCPU_Debug_XamlIntermediateOutputPathBug.g.resources
{
  // Offset: 0x00000000 Length: 0x000003BC
}
.mresource public 'Build/Obj_Exe/XamlIntermediateOutputPathBug_AnyCPU_Debug/XamlIntermediateOutputPathBug.Properties.Resources.resources' as Build_Obj_Exe_XamlIntermediateOutputPathBug_AnyCPU_Debug_XamlIntermediateOutputPathBug.Properties.Resources.resources
{
  // Offset: 0x000003C0 Length: 0x000000B4
}

可以看到ildasm.exe和打开MANIFEST的程序集。还可以看到,普通资源也会得到带有输出路径前缀的错误名称。但是,可以通过在此资源的项目文件中设置LogicalName来修复此问题(在使用MSBuild()构建后运行测试时,请参阅MissingManifestResourceException)。Mresource在manifest中有路径。对于xaml资源,这似乎是不可能的…

查看配置后,我注意到我在OutputPathIntermediateOutputPath的末尾使用/,删除这些似乎可以工作,见下文:

<PropertyGroup>
  <OutputRelativePath>$(ProjectDir)..'Build</OutputRelativePath>
  <OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)</OutputPath>
  <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
  <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)</IntermediateOutputPath>
  <IntDir>$(IntermediateOutputPath)/</IntDir>
  <OutDir>$(OutputPath)/</OutDir>
</PropertyGroup>  

我觉得这很奇怪……对于为什么会出现这种情况,或者这是否真的是真的,任何见解都是值得赞赏的。请注意,c++的IntDirOutDir必须有一个尾随的反斜杠,否则你会得到警告。


为什么修改项目输出目录会导致:IOException未处理&;无法定位资源'app.xaml';

将MSBuild输出的详细程度设置为"Diagnostic",可以快速发现问题的根源:

1>   (TaskId:21)
1>  Microsoft (R) Build Task 'ResourcesGenerator' Version '4.0.30319.33440 built by: FX45W81RTMREL'. (TaskId:21)
1>  Copyright (C) Microsoft Corporation 2005. All rights reserved. (TaskId:21)
1>  
1>   (TaskId:21)
1>  Generating .resources file: '..'Build/Obj_Exe/WpfApplication8_AnyCPU_Debug/WpfApplication8.g.resources'... (TaskId:21)
1>  Reading Resource file: 'C:'Users'hpass_000'Projects'Build'Obj_Exe'WpfApplication8_AnyCPU_Debug'MainWindow.baml'... (TaskId:21)
1>  Resource ID is 'mainwindow.baml'. (TaskId:21)
1>  Generated .resources file: '..'Build/Obj_Exe/WpfApplication8_AnyCPU_Debug/WpfApplication8.g.resources'. 

注意路径名中前后斜杠的混合。Windows本身知道如何处理路径名中的正斜杠。但是这种能力在其他软件中通常是缺乏的,它在资源生成器任务中是缺乏的。它需要一个真正的反斜杠作为路径分隔符,正斜杠在资源名中是有效的。解决办法:

 <OutputPath>$(OutputRelativePath)'$(AssemblyName)_$(Platform)_$(Configuration)'</OutputPath>
 <BaseIntermediateOutputPath>$(OutputRelativePath)'Obj_Exe'$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
 <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)'</IntermediateOutputPath>

换句话说,我只是用'替换了/

当Xaml引用当前项目中的类型时,WinFX和Xaml目标执行一些幕后的hack/magic。在此构建任务期间,wpf。将csproj复制到tempfilename.tmp_proj,修剪与程序集引用相关的几个节点,并将该文件编译为IntermediateOutputPath。这允许Xaml编译器引用临时程序集中的类型。