【问题标题】:Visual Studio 2010 & 2008 can't handle source files with identical names in different folders?Visual Studio 2010 & 2008 无法处理不同文件夹中同名的源文件?
【发布时间】:2011-04-13 08:55:34
【问题描述】:

直接问题:如果我有两个同名的文件(但在不同的目录中),似乎只有 Visual Studio 2005 可以透明地处理这个问题?? VS 2008 和 2010 需要大量调整?除了我的命名约定,我做错了什么吗?

背景:

我正在开发 C++ 统计库...我有两个文件夹:

/ Univariate

Normal.cpp
Normal.h
Beta.cpp
Beta.h
Adaptive.cpp
Adaptive.h

/ Multivariate

Normal.cpp
Normal.h
Beta.cpp
Beta.h
Adaptive.cpp
Adaptive.h

我需要支持交叉编译——我正在使用 g++/make 将这些相同的文件编译到 Linux 中的库中。它们工作得很好。

我一直在使用 Visual Studio 2005 没有问题,但我需要升级到 Visual Studio 2008 或 2010(目前对 nVidia 的 nsight 工具垂涎三尺)。但是,如果我将文件添加到同名的项目中(即使它们位于不同的目录中),我会遇到麻烦。我愿意更改我的命名约定,但我很好奇其他人是否遇到过此问题并找到任何有据可查的解决方案??

如果我从 2005 项目升级到 2010 项目,VS 2010 似乎能够正确处理不同目录中的两个同名源文件,这让我更加困惑。但是,如果我删除其中一个重复文件,然后将其添加回项目中,则会收到以下警告:

Distributions\Release\Adaptive.obj:警告 LNK4042:多次指定对象;多余的被忽略

现在我将中间目录指定为 $(ProjectName)\$(Configuration) -- 我需要将我的目标文件放在与源代码树不同的位置。所以我可以看到为什么它会在彼此之上复制目标文件,但是当项目从 2005 年转换到 2008 年或 2010 年时,会添加一堆条件编译:

<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.xdc</XMLDocumentationFileName>

这些可以从 C/C++ 中的源文件属性页面访问 -> 输出文件 -> “对象文件名”和“XML 文档文件名”。但是,如果我只是直接添加文件(或删除并重新添加它们),VS 在我尝试编译之前不会抱怨,但也永远不会添加条件指令——所以为了让事情正常工作,我必须自己为每个配置添加条件指令。我是在犯错误/假设不当,还是在 VS 2008 / 2010 中发现了一个有效的错误?

标签: c++ visual-studio-2008 visual-studio-2010 naming-conventions projects-and-solutions


【解决方案1】:

所以@Hans Passant 指出了正确的方向,谢谢!!您不必列出文件,一个文件夹就足够了。然后,如果您查看 VS 2010 列表底部的已定义宏,您会看到:

%(RelativeDir)/单变量/

正如发布的那样,问题实际上是我正在处理的问题的简化版本——单个项目中有几个级别的文件夹,并且存在一些名称冲突。因此,我真的很想以某种方式“修复”它......

如果您在解决方案资源管理器中右键单击项目,选择 C/C++ ->“输出文件”,然后在“对象文件名”框中键入以下内容:

$(IntDir)/%(RelativeDir)/

请注意,我还从下拉列表中选择了(所有配置、所有平台)。这将编译镜像源树的目录层次结构中的每个文件。如果这些目录不存在,VS2010 将通过创建这些目录来开始构建。此外,对于那些讨厌目录名称中的空格的人,此宏确实会删除所有空格,因此在使用它时无需使用双引号。

正是我想要的——与我的 Makefile 在 Ubuntu 端的工作方式相同,同时仍保持源代码树的清洁。

【讨论】:

  • 我已经在 Visual Studio 2008 中尝试过这个,但它不起作用。我尝试了几处修改,除了使用其唯一的相对路径分别更改每个文件的属性之外找不到解决方案。
  • 哦,伙计,多年来,由于这个愚蠢的问题,我无法从 VS2005 升级到 VS2008 或 VS2010,除非我想重构一个到目前为止大小为 150k LOC 的项目。最后,我现在成功升级到 VS2010,这绝对让我大吃一惊!我非常感谢你提供这个干净简单的解决方案。让我开心!
  • 我无法在 VS 2013 中使用这些技术。
  • 我的项目比源代码深几级,所以RelativeDir 的值类似于../../src/,但我发现使用$(IntDir)/fake/dir/%(RelativeDir)/ 可以否定这两个.. 部分
  • 最初我无法在 VS 宏中找到 %(RelativeDir),但这篇文章帮助了我。虽然我的对象最终出现在解决方案的顶级根构建中。所以我改用了以下内容:$(IntDir)\$(ProjectName)\$(ConfigurationName)\%(RelativeDir).
【解决方案2】:

这很容易在 IDE 中修复。单击文件夹中的第一个文件,按住 Shift 键并单击最后一个文件,以选中所有文件。右键单击,属性,C++,输出文件。将对象文件名从$(IntDir)\ 更改为$(IntDir)\Univariate\。您可以对 Multivariate 文件组重复此操作,尽管这不是绝对必要的。

【讨论】:

    【解决方案3】:

    你说得对,VS 无法处理,而且永远也无法处理。根本问题是它为项目中的每个.cpp 文件生成一个.obj 文件,并且它们都放在同一个文件夹中。因此,例如,在您的情况下,您最终会得到多个 .cpp 文件编译为 Adaptive.obj

    至少链接器现在会为它生成一个警告。情况并非总是如此。

    您应该能够通过确保文件使用不同的中间目录路径来解决此问题,但对于应该可能的某些事情有点破解。

    当然,您可以随时在 Microsoft Connect 上提交错误报告或功能请求

    【讨论】:

    • 我认为可能已经有a bug for this。推荐的解决方法之一是为您的一个(或两个)目标文件手动更改生成的目标文件名。您应该能够一次覆盖所有配置,因此实施起来应该不会太繁琐。
    • 感谢您的建议,我已将此提交给 Microsoft Connect。正如 Mike Ellery 所指出的,这以前是一个错误,但该技术声称它将在 2010 年修复——我可以确认它不是。如果您或其他人有能力,请随意链接这两个报告:connect.microsoft.com/VisualStudio/feedback/details/599534/…2010 Bug Report
    【解决方案4】:

    请注意,在我的实例中(带有 VS2010 平台工具集的 Visual Studio 2013),使用 $(IntDir)\%(RelativeDir) 无法正常工作,并且在构建多个配置时它会忽略导致链接器错误的中间目录,因为每个配置(即调试和发布)的所有目标文件都放在同一个文件夹中。如果您在切换配置时清理项目,这些就会消失。

    错误示例:

    MSVCRTD.lib(MSVCR100D.dll) : 错误 LNK2005: _fclose 已在 LIBCMTD.lib(fclose.obj) 中定义

    解决方案:

    $(IntDir)\%(目录)

    为了解决这个问题,我必须使用 $(IntDir)\%(Directory) 将所有 *.obj 文件正确放置在中间目录下,并允许构建和链接多个配置而无需清理。唯一的缺点是文件所在的整个(可能很长)文件夹层次结构将在 Debug/Release/etc 文件夹下完全重新创建。

    【讨论】:

    • 感谢%(Directory)。它与%(RelativeDir) 不同,对我来说很重要。
    • 这个解决方案很棒,它适用于项目中上层文件夹的路径,如&lt;ClCompile Include="..\..\..\sources\folder\**\*.cpp" /&gt;
    • 这个应该默认设置!我认为$(IntDir)%(Directory) 就足够了(没有\)
    【解决方案5】:

    此线程中的其他解决方案存在基于 RelativeDir 的 ../.. 问题,并且必须在每个源文件上手动设置。

    更不用说,它们破坏了 /MP。任何为 %(ObjectFileName) 指定精确 .obj 的解决方案都会导致传递给 CL.exe 的每个 .cpp 文件(以将其映射到特定的 .obj 文件)的 /Fo 不同,因此 Visual Studio 无法对它们进行批处理.如果不使用相同的命令行(包括 /Fo)批处理多个 .cpp 文件,/MP 将无法工作。

    这是一种新方法。这至少适用于 vs2010 到 vs2015。将此添加到

    中的 vcxproj
    <!-- ================ UNDUPOBJ ================ -->
    <!-- relevant topics -->
    <!-- https://stackoverflow.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff/26935613 -->
    <!-- https://stackoverflow.com/questions/7033855/msvc10-mp-builds-not-multicore-across-folders-in-a-project -->
    <!-- https://stackoverflow.com/questions/18304911/how-can-one-modify-an-itemdefinitiongroup-from-an-msbuild-target -->
    <!-- other maybe related info -->
    <!-- https://stackoverflow.com/questions/841913/modify-msbuild-itemgroup-metadata -->
    <UsingTask TaskName="UNDUPOBJ_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
      <ParameterGroup>
        <OutputDir ParameterType="System.String" Required="true" />
        <ItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
        <OutputItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
      </ParameterGroup>
      <Task>
        <Code><![CDATA[
                //general outline: for each item (in ClCompile) assign it to a subdirectory of $(IntDir) by allocating subdirectories 0,1,2, etc., as needed to prevent duplicate filenames from clobbering each other
                //this minimizes the number of batches that need to be run, since each subdirectory will necessarily be in a distinct batch due to /Fo specifying that output subdirectory
    
                var assignmentMap = new Dictionary<string,int>();
                HashSet<string> neededDirectories = new HashSet<string>();
                foreach( var item in ItemList )
                {
                  //solve bug e.g. Checkbox.cpp vs CheckBox.cpp
                  var filename = item.GetMetadata("Filename").ToUpperInvariant(); 
    
                  //assign reused filenames to increasing numbers
                  //assign previously unused filenames to 0
                  int assignment = 0;
                  if(assignmentMap.TryGetValue(filename, out assignment))
                    assignmentMap[filename] = ++assignment;
                  else
                    assignmentMap[filename] = 0;
    
                  var thisFileOutdir = Path.Combine(OutputDir,assignment.ToString()) + "/"; //take care it ends in / so /Fo knows it's a directory and not a filename
                  item.SetMetadata( "ObjectFileName", thisFileOutdir );
                }
    
                foreach(var needed in neededDirectories)
                  System.IO.Directory.CreateDirectory(needed);
    
                OutputItemList = ItemList;
                ItemList = new Microsoft.Build.Framework.ITaskItem[0];
    
            ]]></Code>
      </Task>
    </UsingTask>
    
    <Target Name="UNDUPOBJ">
      <!-- see stackoverflow topics for discussion on why we need to do some loopy copying stuff here -->
      <ItemGroup>
        <ClCompileCopy Include="@(ClCompile)"/>
        <ClCompile Remove="@(ClCompile)"/>
      </ItemGroup>
      <UNDUPOBJ_TASK OutputDir="$(IntDir)" ItemList="@(ClCompileCopy)" OutputItemList="@(ClCompile)">
        <Output ItemName="ClCompile" TaskParameter="OutputItemList"/>
      </UNDUPOBJ_TASK>
    </Target>
    <!-- ================ UNDUPOBJ ================ -->
    

    然后修改 使其变为:

    <Project InitialTargets="UNDUPOBJ" ...
    

    结果将类似于 myproj/src/a/x.cpp 和 myproj/src/b/x.cpp 编译为 Debug/0/x.obj 和 Debug/1/x.obj。 RelativeDirs 没有被使用,所以不是问题。

    此外,在这种情况下,将只有两个不同的 /Fo 传递给 CL.exe:Debug/0/ 和 Debug/1/。因此,向 CL.exe 发出的批处理不会超过两个,从而使 /MP 更有效地工作。

    其他方法是将 .obj 子目录基于 .cpp 子目录,或者使 .obj 文件名包含原始 .cpp 目录的一些纪念品,以便您可以轻松查看 .cpp->.obj 映射,但那些导致更多 /Fo 并因此减少批处理。未来的工作可能会转储一个映射文件以供快速参考。

    有关 /MP 和批处理的更多详细信息,请参阅此处:MSVC10 /MP builds not multicore across folders in a project

    我已经在生产环境中在各种工具链上的 vs2010 和 vs2015 上对此进行了一段时间的测试。它似乎是防弹的,但它总有可能与其他 msbuild 自定义或外来工具链交互不良。

    从 vs2015 开始,如果您收到警告“警告 MSB8027:两个或多个名为 X.cpp 的文件将产生输出到同一位置”,那么您可以将其添加到您的项目或 msbuild 文件中:

    <PropertyGroup Label="Globals"><IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename></PropertyGroup>
    

    https://connect.microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-buildHow to suppress specific MSBuild warning 上查看更多信息


    2019 年 9 月更新:

    我认为上面存在一个问题,即 ItemList 可以任意排序,从而导致给定源文件重新分配到不同编号的目录。因此,每当源文件列表更改时,可能会出现对象的重复副本。这也可能导致依赖跟踪变得有点混乱,因为输入->输出映射可能会发生惊人的变化,因此时间戳没有意义。

    我们可以通过散列已排序的 ItemList 并将其添加到输出目录的前缀来解决此问题,但这会产生额外的重建和大量垃圾。

    这可以通过删除一个列出输入->输出映射的文件来改进,并将源文件到目标编号目录的映射作为一次性事件(直到项目被清理)。

    我还没有对此进行更深入的调查。

    【讨论】:

      【解决方案6】:

      使用配置属性> C/C++ > 输出文件> $(IntDir)\%(RelativeDir)\%(Filename)

      这将复制调试目录下的源文件结构,并将每个目录的目标文件存放在Debug目录下的同名文件夹中

      【讨论】:

      • 已确认 VS2013。我把 $(IntDir)%(RelativeDir) 放在 Configuration Properties > C/C++ > Output Files > Object File Name
      【解决方案7】:

      您也可以在 Visual Studio 中创建一个 .cpp 文件,然后将其重命名为 .h。尽管文件被重命名,但 Visual Studio 仍将其编译为 cpp 文件,因此会创建两个 obj 文件并显示链接器警告。

      【讨论】:

        【解决方案8】:

        %(RelativeDir) 解决方案仅适用于 Visual Studo 2010。

        对于 Visual Studio 2008,您需要右键单击每个重复的 .cpp 文件名并选择“属性”。这可能并不明显,但您实际上可以为每个单独的文件修改“配置属性 -> C/C++ -> 输出文件”部分。

        在每个重复的 .cpp 文件的“对象文件名”设置中添加一个子文件夹,注意 .h 文件不需要修改,因为 .cpp 文件决定了 .obj 文件的输出。

        例如,如果您的项目有两个冲突的文件:

        内部\example.cpp 基础\example.cpp

        您可以将每个“对象文件名”设置为:

        $(IntDir)\内部\ $(IntDir)\base\

        您需要为所有配置发布/调试等执行此操作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-21
          • 1970-01-01
          • 1970-01-01
          • 2020-01-13
          相关资源
          最近更新 更多