为什么修改项目输出目录会导致:IOException was unhandled "Cannot locate resource 'app.xaml'."

9
为了将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样式更新:如果这些资源字典(例如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来解决此问题(请参见使用MSBuild构建后运行测试时出现MissingManifestResourceException(.mresource在清单中具有路径))。但似乎对于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发生的更多信息? - Nicodemeus
2个回答

4
将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>

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接