Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
325 views
in Technique[技术] by (71.8m points)

c# - Why does modifying project output directories cause: IOException was unhandled "Cannot locate resource 'app.xaml'."

In an attempt to consolidate project settings into property sheets for both C++ and C# projects, the following property sheet was constructed:

<?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>

This property sheet will move all build output to a separate location OutputRelativePath (defined in separate property sheet or directly in project file) outside directories that contain source code for easier cleanup etc. However, after setting this up and build works fine and all unit tests work fine, it was clear that a WPF executable project was not fine, since running the application with above property sheet results in the infamous:

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

Why does changing the output paths result in this error? And how can it be determined that the cause is project build output paths? Can this be seen in generated code? I could not find it? And isn't this a bug?

NOTE: Using the following property sheet works, but only if IntermediateOutputPath contains 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>

So it appears, that somehow it is expected that output paths contain the AssemblyName properties or similar.

UPDATE FOR XAML STYLES IN ANOTHER ASSEMBLY: The same applies to xaml ResourceDictionary if these - e.g. Brushes.xaml - are located in another assembly and this assembly has changed the OutputPath also, this also throws an exception:

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

So all in all it appears output location changes the xaml resource names so these cannot be discovered at runtime, somehow. The odd thing is it is not a problem at design time...


UPDATE: Minimal steps to reproduce the exception:

Open Visual Studio 2013

Create new C# project WPF Application e.g. XamlIntermediateOutputPathBug

Unload Project

Edit Project File

After first PropertyGroup insert new PropertyGroup as:

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

Delete OutputPath properties in remaining PropertyGroups e.g.

<OutputPath>binDebug</OutputPath>  

and:

<OutputPath>binRelease</OutputPath>  

This should then throw an IOException on start for mainwindow.xaml. This is due to the $(AssemblyName).g.resources embedded resource is given the following name:

.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
}

as can be seen with ildasm.exe and opening the MANIFEST for the assembly. As can also be seen the normal resources also gets a wrong name with the output path prefixed. This can, however, be fixed by setting the LogicalName in the project file for this resource (see MissingManifestResourceException when running tests after building with MSBuild (.mresource has path in manifest)). This does not appear to be possible for xaml resources...

Having looked at the configuration I noticed I use / at the end of the OutputPath and IntermediateOutputPath, removing these it appears to work, see below:

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

I find this rather curious... any insight into why this would be the case or if this is actually true is appreciated. Note that the C++ IntDir and OutDir instead must have a trailing backslash, otherwise you will get warnings about this.


See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Setting the MSBuild output verbosity to "Diagnostic" quickly revealed the source of the problem:

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:Usershpass_000ProjectsBuildObj_ExeWpfApplication8_AnyCPU_DebugMainWindow.baml'... (TaskId:21)
1>  Resource ID is 'mainwindow.baml'. (TaskId:21)
1>  Generated .resources file: '..Build/Obj_Exe/WpfApplication8_AnyCPU_Debug/WpfApplication8.g.resources'. 

Note the mix of forward and backward slashes in the path names. Windows itself knows how to handle forward slashes in path names well. But that capability is often lacking in other software, it is lacking in the resource generator task. Which requires a true backslash as a path separator, a forward slash is valid in a resource name. Fix:

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

In other words, I simply replaced / with . Which solved the problem.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...