为啥修改项目输出目录会导致:IOException is unhandled “Cannot locate resource 'app.xaml'.”

Posted

技术标签:

【中文标题】为啥修改项目输出目录会导致:IOException is unhandled “Cannot locate resource \'app.xaml\'.”【英文标题】:Why does modifying project output directories cause: IOException was unhandled "Cannot locate resource 'app.xaml'."为什么修改项目输出目录会导致:IOException is unhandled “Cannot locate resource 'app.xaml'.” 【发布时间】:2013-06-13 09:15:11 【问题描述】:

为了将项目设置合并到 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,如果这些 - 例如Brushes.xaml - 位于另一个程序集中,并且该程序集也更改了 OutputPath,这也会引发异常:

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

总而言之,输出位置似乎改变了 xaml 资源名称,因此无法在运行时以某种方式发现这些资源名称。奇怪的是它在设计时不是问题......


更新:重现异常的最少步骤:

打开 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 来解决此问题(请参阅MissingManifestResourceException when running tests after building with MSBuild (.mresource has path in 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 和 XamlParseException 的信息? 【参考方案1】:

将 MSBuild 输出详细程度设置为“诊断”很快就揭示了问题的根源:

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>

换句话说,我只是将/ 替换为\。这解决了问题。

【讨论】:

【参考方案2】:

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

【讨论】:

以上是关于为啥修改项目输出目录会导致:IOException is unhandled “Cannot locate resource 'app.xaml'.”的主要内容,如果未能解决你的问题,请参考以下文章

java中输出流不关闭为啥不能写入

为啥将调用函数提前 1 个字节会导致它发生故障?修改啥来解决?

为啥啁啾频率的微小变化会导致快速傅里叶变换输出发生重大变化?

python 为啥我修改列表一个元素会导致所有元素都被修改

Python - 直接扩展列表会导致无,为啥?

你能解释一下为啥 DirectoryInfo.GetFiles 会产生这个 IOException 吗?