在 Visual Studio 本地引用 Boost (MSBuild) 已翻译 100%

oschina 投递于 2015/03/19 16:32 (共 11 段, 翻译完成于 04-01)
阅读 4133
收藏 51
1
加载中

介绍

这是关于将第三方工具和库集成到 Visual Studio 系列中的第四篇文章。在第一篇文章中我解释了如何创建 Visual Studio 属性对话框的自定义属性页。第二篇文章涵盖了属性表的内部结构和元素。第三篇文章通过构建 Boost 库的例子解释了如何创建自定义构建。本文是第四篇,我将解释如何集成自定义构建到 Visual Studio 项目的引用系统。

基本原理

每个 C++ 项目由几个较小的子项目和库组成。它们在编译或运行时被用于链接,需要被适当地引用。如果所有的项目都是在 Visual Studio(MSBuild)中被创建的,那么引用是由 MSBuild 来负责。但当一个项目或库来自外部时,我们不得不通过手动配置来适当地集成。

霍啸林
霍啸林
翻译于 2015/03/19 19:43
1

理想情况下,我们应该能通过在 Visual Studio 中添加对某个项目的引用来将其集成到 MSBuild 中:

如果我们对任何库都能这么做,那不是很好吗?如果所有的 lib 文件会自动添加到 LINK 命令,同时所有的 DLL 文件都复制到输出目录,那么他们不就可以在运行时被链接上吗?如果既能调试我们的代码,还能调试库的代码,那又会怎样呢?

在这篇文章中,我将告诉你这该如何做到。我会用库来演示如何将它集成到任何项目,却无需手动操作库或设置路径。我假设你已经知道该如何构建 Boost,不知道的话就请读这篇文章

霍啸林
霍啸林
翻译于 2015/03/19 20:00
1

背景资料

当 Visual Studio 从一个项目添加引用到另一个项目时,它会像下面这样将一条记录添加到主项目中:

<ProjectReference Include="...\boost.vcxproj">
    <Project>{9cd23c68-ba74-4c50-924f-2a609c25b7a0}</Project>
    ...
</ProjectReference>

关于引用是如何被添加的详细信息,参见这个链接

在构建主项目的过程中,MSBuild 会对 ProjectReference 段中列出的所有依赖进行解析和构建。它会定位列出的子项目,并通过对每个子项目调用下列 Target 来收集必要的信息:

GetTargetPath
GetNativeManifest
GetResolvedLinkLibs
GetCopyToOutputDirectoryItems

我将简要地解释它们分别做了什么。

霍啸林
霍啸林
翻译于 2015/03/19 20:37
1

GetTargetPath

这个目标(Target)返回项目构建的程序集/库的完整路径。在设计阶段,Visual Studio使用这个文件来判断引用是否正确,以及是否可以找到输出文件。如果程序集是托管类型,Visual Studio也会查询它以获取更多的信息。
理论上讲,只要这个路径指向已经存在的文件,引用系统都会正常报告引用是有效的。

对于Boost库而言,没有单一的库文件。它依据配置,构建任意数量的库文件,或者根本就不构建库文件。我们可以使用这些来进行引用校验。我们可以返回指向任意文件的路径,来表明引用是有效的。我决定返回文件Jamroot的路径,用来表明,本次构建是使用哪个源代码来创建的库文件:

<Target Name="GetTargetPath" Returns="@(TargetPath)" >
  <ItemGroup>
    <TargetPath Include="$(BoostRoot)\Jamroot">
      <Private>true</Private>
      <FileType>info</FileType>
      <ResolveableAssembly>false</ResolveableAssembly>
    </TargetPath>
  </ItemGroup>
</Target>

它需要在项目(Item)上设置如上所示的一些元数据( metadata)属性。FileType通常包含如lib或dll为拓展名的文件,由于在这里不适用,所以我返回了假的类型。ResolveableAssembly表明,它是托管程序集或者是原生的。Private包含了本地复制(Local Copy)设置。

gones945
gones945
翻译于 2015/03/27 22:09
1

GetNativeManifest

如果由于某种原因,子项目必须重新发布Manifest文件以及库文件,这个目标(Target)会返回manifest文件的列表信息。父工程会简单的拷贝这些manifest文件到输出目录。

Boost无需任何manifest文件,所以它不用做任何设置:

<Target Name="GetNativeManifest" />

GetResolvedLinkLibs

这个目标(Target)返回所有链接库的列表信息。它们将会添加到LINK命令,这样这些lib文件就可以链接了。Boost库针对它创建的每个模块都有一个lib文件。

对我们来说,要返回正确的列表信息,首先要获取创建的库文件的列表信息,然后链接到实际的lib文件。我们需要完成两个步骤:

  • 使用当前选项 以及 --show-libraries 命令调用b2(GetBuiltLibs)

  • 处理链接库的引用,并将它们加入返回列表(GetResolvedLinkLibs)

<Target Name="GetBuiltLibs" DependsOnTargets="BuildJamTool" Returns="@(BuiltLibs)" >
  <Exec Command="b2.exe @(boost-options, &apos; &apos;) --show-libraries" ... />
    
    <ReadLinesFromFile Condition="Exists(&apos;$(TempFile)&apos;)" File="$(TempFile)">
      <Output TaskParameter="Lines" ItemName="RawOutput" />
    </ReadLinesFromFile>
    <Delete Condition="Exists(&apos;$(TempFile)&apos;)" Files="$(TempFile)"/>
    
    <ItemGroup>
      <BuiltLibs Include="$([Regex]::Match(%(RawOutput.Identity), (?<=\-\s)(.*) ))" />
    </ItemGroup>
  </Target>

请注意:为了清晰起见,文中所有的示例代码都做了简化处理。

<Target Name="GetResolvedLinkLibs" DependsOnTargets="GetBuiltLibs" Returns="@(LibFullPath)">
  <ItemGroup>
    <LibFullPath Include="$(OutputDir)\lib\*boost*%(BuiltLibs.Identity)*.lib">
      <ProjectType>StaticLibrary</ProjectType>
      <FileType>lib</FileType>
      <ResolveableAssembly>false</ResolveableAssembly>
    </LibFullPath>
  </ItemGroup>
</Target>

当库列表信息返回后,我们几乎不用为每个项目设置元属性。

gones945
gones945
翻译于 2015/03/30 22:20
1

GetCopyToOutputDirectoryItems

这个目标(target)返回内容文件的列表信息,这些文件需要拷贝到主工程的输出目录。 它们可以是任意类型的文件。对于Boost库来说,它们是构建过程中创建的所有dll文件。我们使用,与之前一样的算法,来列出这些文件:

<Target Name="GetCopyToOutputDirectoryItems" DependsOnTargets="GetBuiltLibs" Returns="@(DLLToCopy)" 
        Condition="&apos;$(boost-link)&apos;==&apos;DynamicLibrary&apos;" >
  <ItemGroup>
    <BoostDlls Include="$(OutputDir)\lib\*boost*%(BuiltLibs.Identity)*.dll" />
    <DLLToCopy Include="@(BoostDlls)" Condition="&apos;%(BoostDlls.Identity)&apos;!=&apos;&apos;" >
      <TargetPath>%(FileName).dll</TargetPath>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </DLLToCopy>
  </ItemGroup>
</Target>


在上面的代码中,每个项目需要设置两个元数据(Metadata)属性:TargetPath和CopyToOutputDirectory。

TargetPath包含文件名和拓展名。当拷贝到目标文件夹,类似这样:$(DestinationFolder)$(TargetPath)的时候,它被用来指定文件名。

CopyToOutputDirectory包含两个可能的值:Always和PreserveNewest,其中之一。
 它告知构建系统,要么总是拷贝文件,要么只拷贝源文件比目标文件新的文件。 

对Boost库来说,如果是最新的,就无需拷贝DLL文件。

现在,如果我们将boost工程作为引用添加进来,它将会注册为有效的,同时提供父工程在正确构建过程中可能需要的所有信息。

gones945
gones945
翻译于 2015/03/31 23:07
1

基于 Boost 进行构建

我们开始用一个非常原始的名字(Sample)来创建一个简单控制台应用程序。鉴于每个人都知道如何在 Visual Studio 中创建一个控制台应用程序,我将跳过相关的步骤说明。

boost 项目添加至解决方案。

前往 Sample 项目的属性页,添加对 boost 项目的引用。你会看到像这样的界面:

如图所示,boost 项目已被正确地引用并指向 Boost 库安装的 D:\Boost 目录。由于 Boost 不是一个托管程序集,程序集标识(Assembly Name)、区域性(Culture)、版本号(Version)、描述(Description)都不可用。

霍啸林
霍啸林
翻译于 2015/03/19 21:17
1

值得注意的是,Copy Local 属性用来确定库是否要被复制到引用它的项目的输出目录。如果子项目生成的是托管程序集或只是一个lib 文件,那是不会出问题的。但如果子项目生成的是原生 DLL 或多个库的话,整个过程就会中断。我们通过重新定义 GetCopyToOutputDirectoryItems 来修复它。我们现在要来控制是否将 DLL 复制到主项目的输出目录,那就需要向 Boost 属性页的常规选项卡添加额外的属性:

将这个属性设置为 No 可以禁用复制。这个设定只在 Boost 库是以共享的方式被构建时才起作用,对生成静态库是无效的。

霍啸林
霍啸林
翻译于 2015/03/19 21:37
1

增量构建

每当我们构建的时候,b2 会检查配置并决定它是否要构建组件的一部分。当 Boost 被用来开发其他项目时,它本身不大会发生什么变动。所以检查是否发生变动基本上是多余的。我已经在属性页的常规选项卡中增加了一个禁用这种检查的选项:

当这个选项是 Yes 或者空白时,对重新构建的检查会被委派给 Visual Studio。它检查时会比对输出库的列表、已配置库的列表以及项目文件本身。若有任何库被删除或项目设置发生变更,它便会进行构建。否则它会跳过构建,使得每次构建的耗时节省大概半分钟。要重新启用这个检查,请将此选项设为 No

将这些检查委派给 Visual Studio 需具备以下要素:

霍啸林
霍啸林
翻译于 2015/03/19 21:59
1

构建输出

通过测试输出命令:b2 --show-libraries,可以推断出构建的库列表。一旦我们有了列表,通过调用Target GetBoostOutputs来验证库中所呈现的东西。

<Target Name="GetBoostOutputs" DependsOnTargets="GetBuiltLibs" Returns="@(BoostOutputs)" >

  <ItemGroup>
    <BoostOutputs Include="$(OutputDir)\lib\*boost*%(BuiltLibs.Identity)*.lib" >
       <Library>%(BuiltLibs.Identity)</Library>
    </BoostOutputs>
    <ExistingLibs Include="%(BoostOutputs.Library)" />
    <BoostOutputs Include="@(BuiltLibs)" Exclude="@(ExistingLibs)" 
                  Condition="&apos;@(ExistingLibs->Count())&apos;!=&apos;@(BuiltLibs->Count())&apos;" />
    <BoostOutputs Include="%(BoostOutputs.RootDir)%(BoostOutputs.Directory)%(BoostOutputs.Filename).dll"                  Condition="&apos;@(BoostOutputs0>Filename->StartsWith(&#34;boost_&#34;))&apos;==&apos;true&apos; And 
                             &apos;%(BoostOutputs.Library)&apos;!=&apos;&apos; And &apos;$(boost-link)&apos;==&apos;DynamicLibrary&apos;" />
  </ItemGroup>
</Target>

正如你上面所看到的那样,我们从GetBuiltLibs目标中得到了库的列表,而且查找到所有*boost*<library-name>*.lib样子的lib文件。既返回了包含动态链接库也返回了包含了静态库。

下一步我们将在构建的库文件的列表上创建内部连接,使用它来查找漏掉的库。

紧接着,我们添加漏掉的库到BoostOutputs为了在需要是使用。

然后我们添加动态链接库。

列表将由Build Target来确定是否需要执行检查。

dust_wang
dust_wang
翻译于 2015/03/30 22:12
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(9)

m
magiclogy

引用来自“FlashCHen”的评论

JAVA的配置都笑了
C/C++世界没有统一的构造工具,所以不好比。。。
m
magiclogy
构造系统选择图形界面,可能是一种错误吧。
USIDCBBS
USIDCBBS
没这么复杂吧,BOOST大多数库不需要LIB和DLL,直接include
e
evilwk
感觉是疯了
明月惊鹊
明月惊鹊
不复杂怎么吓人啊,O(∩_∩)O哈哈~
假红薯
我就说VS太tm庞大和复杂了把。。。

马勒戈壁的的 不就是加上boost吗 整这么复杂 真是抽风了吧

这种配置就是傻逼到无以复加的地步。

实际上很简单 在编译选项里 加上 -I 后面写上boost解压后的路径即可。



-土星-
-土星-
这样重新编译是不是巨慢?
FlashCHen
FlashCHen
JAVA的配置都笑了
uudiin
uudiin
微软的东西灵活性还真TMD好啊,配置个工程麻烦死
返回顶部
顶部